Modern people are trying to spend less and less time on shopping. Slow product catalogs drive away customers, the store loses customers and part of its profits. Make your online store more attractive with facet technology Facet - i.e. predefined. search. Create faceted indexes and significantly speed up the search for products and the work of the entire catalog.
Note: The faceted search mechanism is available from version 15.0.1 of the Information Blocks module and is integrated with the component. A component is a program code designed in a visual shell that performs a specific function of a module to display data in the Public part. We can insert this block of code into website pages without writing any code directly. Smart filter The component prepares a filter for selecting from an information block and displays a filter form for filtering elements. The component must be connected before the component for displaying catalog elements, otherwise the list of elements will not be filtered. The component is standard, included in the module distribution and contains three templates: .default , visual_horizontal and visual_vertical . (The last two templates are not supported, they remain to maintain compatibility.)
In the visual editor, the component is located along the path Content > Catalog > Smart filter.
The component belongs to the Information blocks module.
Let's look at an example: We go to the online store and select in the filter that we need a red T-shirt:
|
Let's create faceted indexes in a few simple steps:
Faceted indexes are recreated automatically or you need to recreate them manually, depending on the actions performed:
|
Faceted search improves the performance of the product catalog. To use it you need:
Built-in faceted search Built into the product The faceted search built into the online store - internal search - works quickly in many respects and does not load the system.
The client is instantly presented with pre-prepared search results - for any combination of parameters - facet. The system calculates facets for a product in advance - all possible intersections of these properties in the filter. These ready-made search sets are then issued to clients. Why doesn't the site load? At the moment the result is issued to the client, no calculations occur, because the result is already ready. A facet for a new product is created immediately when it is added to the sales catalog. The search is automatically re-indexed based on new products and new properties. Benefits for clients Advantages of faceted search Your client finds the product very quickly, easily playing with the filter settings. The client does not wait and gets results instantly. Search speed does not depend on the number of items in the catalog.
Your client finds the product very quickly by consistently narrowing the search query. And at every step, he instantly receives results - a list of products upon request. He does not have to wait for his request to be processed. Because the system calculated everything in advance possible options, and simply issues blanks. Gradually, the online store displays fewer and fewer products as results. These products are getting closer and closer to the buyer's request. Interactivity and multidimensionality Choosing a product for a buyer is like a game. The client drags parameters (price, weight), switches properties (color, size), sets additional conditions (brand, material, taste, etc.) - and the system immediately rearranges the results. In this case, there can be as many customizable parameters as you like - their number does not affect the speed of generating the result. Convenience and friendliness With faceted navigation, even an inexperienced user can easily select a product in the store. Manipulating search tools is very convenient. In addition, the system prompts the buyer with all the parameters by which he can choose a product. The store, as it were, demonstrates to the client the main properties of the product. The client does not wait for the request to be processed! Search speed Search speed matters Search speed affects the number of purchases madeNothing irritates a customer more than having trouble finding products on your website. The client will leave for other stores if he searches for too long. Even if your store contains big choice products and many tools for filtering them. The client may not receive results from the request.
Why is search so slow?
Fast even without a “facet”! The product is constantly working to speed up the catalog components themselves. The “Site Speed” service shows a significant increase in speed from version to version! Reconstruction Constant reconstruction of indexing and search results is carried out. The content indexing algorithm is being reworked and accelerated. The quality of presentation of search results is improved - in particular, “noise” is reduced. The developers plan to display personalized data for the current client in search results. For Developers: API Transparency
Full integration with smart filter Now, when making settings in the administrative part, for product properties you can not only indicate the activity - whether to participate or not in the smart filter. By passing a property to the Smart Filter, you can immediately choose how to display them. In what form should the property be shown to clients: buttons, sizes, sliders, drop-down lists, lists with color selection, etc.
The smart filter now looks more beautiful. Developers can easily customize and further customize its appearance. In today's lesson we will try to recreate an imitation of faceted search using Javascript. I'm assuming you already know what faceted search is if you're reading this tutorial, otherwise google it or check out Amazon or my Demo. First we need the library github.com/eikes/facetedsearch. Download it and connect the facetedsearch.js file to our project. We will also need the jQuery and Underscore libraries. Disclaimer: I understand that JQ is no longer a cake, but I use it as familiar syntactic sugar, you can rewrite it for libraries more familiar to you or in vanilla JS. So, first, let's make a simple markup with connected dependencies: Document // Here we will display facet filters // And here our elements will be Now we need to describe the settings of our application and create a template for displaying array elements that we will sort using facets: $(function())( var item_template = // Describe the template "" + " " class="img-responsive">" + ", " + " " + "" + ""; settings = ( items: example_items, facets: ( // Specify facet categories "category" : "What Category", "continent" : "Which Continent", "language" : "Programming Language"), resultSelector: "#results", // DOM element where we display the results facetSelector: "#facets", // DOM element for facets resultTemplate: item_template, paginationCount: 8, // Number of elements per page orderByOptions: ("firstname": "First name ", "lastname": "Last name", "category": "Category", "RANDOM": "Random"), facetSortOption: ("continent": ["North America", "South America"]) ) $. facetelize(settings); ));Well, actually create a JSON array itself with elements to display in our faceted search in JS: Var items = [ ( "firstname": "Mary", "lastname": "Smith", "imageURL": "http://lorempixel.com/150/150/cats/2", "description": "Sed Ea Amet. Stet Voluptua. Nonumy Magna Takimata ", "category": "Mouse", "language": ["Smalltalk", "XSLT"], "continent": "Africa" ), ( "firstname": "Patricia", "lastname": "Johnson", "imageURL": "http://lorempixel.com/150/150/cats/3", "description": "Ut Takimata Sit Aliquyam Labore Aliquyam Sit Sit Lorem Amet. Ipsum Rebum." , "category": "Lion", "continent": "North America", ... ]; I would put this array into a separate JS file that would be generated dynamically, from a database, for example. That's all, we get a faceted search in JavaScript and can customize it. Next, I provide translated documentation of the library, where you can see the triggers you need. Documentation FeaturesTwo functions are exported to the jQuery namespace. facetelize Used to initialize a faceted search with the given settings. facetUpdate Can be used if you want to change the facet lookup state externally. Object settingsitems: An array of items that will be filtered and sorted in the process. facets: An object whose keys correspond to element keys and values is the header for that facet. Items will be filtered based on what value they have for these keys. orderByOptions: Similar to facets, except these key-value pairs are used only for sorting. When the RANDOM key is enabled, the results can be randomized. facetSelector: This is a selector that is used to find a DOM node from which to select facet filters. resultSelector: This is a selector that is used to find the DOM node where results are displayed. resultTemplate: A string that is used by the Underscore template engine to render each element from the items array. The following attributes are added to each element, which can also be used in the template: batchItemNr, batchItemCount, and totalItemCount. state: This object stores the current filters, sorts: currentResult and others. You can provide an orderBy string or a filters object to preset them. enablePagination: Boolean to enable pagination and the "load more" button, defaults to true . paginationCount: If paginator is enabled, sets the number of elements per page, default is 50. facetSortOption: Use this function to change the order of facet elements. Takes an object whose keys correspond to facet names and values into an array of facet values, which can be arranged in the order you would like them to be. This example will sort the continents in a different order, adding items not included in the array in alphabetical order: FacetSortOption: ("continent": ["North America", "South America"]) There are some more templates, please have a look source facetedsearch.js to see all available template options. EventsYou can bind to some events which should send notifications when some actions happened. To do this, we use the jquery event system: facetuicreated: You can bind this function to DOM element settings.facetSelector which should be notified when the UI has been created. facetedsearchresultupdate: You can bind this function to the settings.resultSelector DOM element to be notified of the update results. facetedsearchfacetclick: This event is fired when a facet is clicked and fired on the settings.facetSelector element. Which receives the facet id as an argument. facetedsearchorderby: This event is fired when the sorting element is clicked on the settings.facetSelector element. It takes the ID order as an argument. $(settings.resultSelector).bind("facetedsearchresultupdate", function())( // do something, maybe )); We took a quick look at the installation and basic syntax of PINQ, a port of LINQ to PHP. In this article, we'll look at how to use PINQ to simulate the faceted search feature in MySQL. In this article we will not cover all aspects of faceted search. Interested people can search for suitable information on the Internet. A typical faceted search works like this:
Faceted search is quite popular and a powerful tool that can be seen on almost any e-commerce website. Unfortunately, faceted search is not built into MySQL. So what should we do if we still use MySQL, but want to give the user this opportunity? With PINQ, which has a similar, powerful and simple approach, we can achieve the same behavior as if we were using other database engines. Expanding the demo from the first partNote: all code from this part, and from the first part, can be found in the repository. In this article, we'll expand on the demo from Part 1 with a significant improvement in the form of faceted search. Let's start with index.php by adding following lines: $app->get("demo2", function () use ($app) ( global $demo; $test2 = new pinqDemo\Demo($app); return $test2->test2($app, $demo->test1 ($app)); )); $app->get("demo2/facet/(key)/(value)", function ($key, $value) use ($app) ( global $demo; $test3 = new pinqDemo\Demo($app); return $test3->test3($app, $demo->test1($app), $key, $value); )); The first route takes us to a page to view all entries that match the search by keyword. To keep the example simple, we select all books from the book_book table. It will also display the resulting data set and a set of links to specify the search criteria. In real applications, after clicking on such links, all facet filters will adjust to the boundary values of the resulting data set. The user will thus be able to sequentially add new search conditions, for example, first select a manufacturer, then specify a price range, etc. But in this example we will not implement this behavior - all filters will reflect the boundary values of the original data set. This is the first limitation and the first candidate for improvement in our demo. As you can see in the code above, the actual functions are located in another file called pinqDemo.php. Let's take a look at the corresponding code that provides the faceted search feature. Aspect classThe first step is to create a class that represents an aspect. In general, an aspect should contain several properties:
Grouping is the most critical part of the aspect. All aggregated information that an aspect may be able to return depends on the grouping criteria. Typically the most used search criteria are “Full String”, “Part of String”, or “Range of Values”. Namespace classFacet ( use Pinq\ITraversable, Pinq\Traversable; class Facet ( public $data; // Original data set public $key; // field by which to group public $type; // F: entire row; S: start strings; R: range; public $range; // only plays a role if $type != F ... public function getFacet() ( $filter = ""; if ($this->type == "F") // entire line ( ... ) elseif ($this->type == "S") // start of line ( ... ) elseif ($this->type == "R") // range of values ( $ filter = $this->data ->groupBy(function($row) ( return floor($row[$this->key] / $this->range) * $this->range; )) ->select(function (ITraversable $data) ( return ["key" => $data->last()[$this->key], "count" => $data->count()]; )); ) return $filter; ) ) ) The main function of this class is to return a filtered dataset based on the original dataset and aspect properties. From the code it is clear that for various types accounts use different ways to group data. In the code above, we showed what the code might look like if we group the data by a range of values in increments specified in $range . Setting aspects and displaying source dataPublic function test2($app, $data) ( $facet = $this->getFacet($data); return $app["twig"]->render("demo2.html.twig", array("facet" = > $facet, "data" => $data)); ) private function getFacet($originalData) ( $facet = array(); $data = \Pinq\Traversable::from($originalData); // 3 creation examples different aspect objects, and return the aspects $filter1 = new \classFacet\Facet($data, "author", "F"); $filter2 = new \classFacet\Facet($data, "title", "S", 6) ; $filter3 = new \classFacet\Facet($data, "price", "R", 10); $facet[$filter1->key] = $filter1->getFacet(); $facet[$filter2->key ] = $filter2->getFacet(); $facet[$filter3->key] = $filter3->getFacet(); return $facet; ) In the getFacet() method we do the following:
In most cases, filters will be displayed as a line, and will lead you to view the filtered result. We've already created a route ("demo2/facet/(key)/(value)") to display faceted search results and filter links. The route takes two parameters, depending on the key being filtered by and the value for that key. The test3 function that is bound to this route is shown below: Public function test3($app, $originalData, $key, $value) ( $data = \Pinq\Traversable::from($originalData); $facet = $this->getFacet($data); $filter = null; if ($key == "author") ( $filter = $data ->where(function($row) use ($value) ( return $row["author"] == $value; )) ->orderByAscending( function($row) use ($key) ( return $row["price"]; )) ; ) elseif ($key == "price") ( ... ) else //$key==title ( .. . ) return $app["twig"]->render("demo2.html.twig", array("facet" => $facet, "data" => $filter)); ) Basically, depending on the key, we apply filtering (an anonymous function in the where statement) according to the passed value and get the following set of filtered data. We can also set the order of data filtering. Finally, we display the raw data (along with filters) in the template. This route uses the same pattern we used in "demo2". Search Bar
We need to remember that the aspects generated by our application are nested arrays. At the first level, this is an array of all aspects, and, in our case, there are three of them (for author, title, price, respectively). Each aspect has a key-value array, so we can iterate over it using normal methods. Notice how we build the URLs for our links. We use both the outer loop key (k) and the inner loop keys (vv.key) as parameters for the route ("demo2/facet/(key)/(value)"). The size of the arrays (vv.count) is used for display in the template. The first image shows the original data set, and the second image is filtered by price range from $0 to $10, and sorted by author. Great, we were able to simulate faceted search in our application! Before concluding this article, we need to take a final look at our example and determine what can be improved and what limitations we have. Possible improvementsIn general, this is a very basic example. We just walked through basic syntax and concepts and implemented them as a working example. As previously stated, we have several areas that could be improved for greater flexibility. We need to implement “overlay” search criteria, since the current example limits us to the ability to apply search filtering only to the original data set; we cannot apply faceted search to an already filtered result. This is the biggest improvement I can imagine. RestrictionsThe facet search implemented in this article has serious limitations (which may also apply to other facet search implementations): We fetch data from MySQL every time This application uses the Silex framework. Like any single entry point framework like Silex, Symfony, Laravel, its index.php (or app.php) file is called every time a route is parsed and controller functions are executed. If you look at the code in our index.php, you will notice that the following line of code: $demo = new pinqDemo\Demo($app); is called every time the application page is rendered, which means the following lines of code are executed each time: Class Demo ( private $books = ""; public function __construct($app) ( $sql = "select * from book_book order by id"; $this->books = $app["db"]->fetchAll($sql ); ) Will it be better if we don't use a framework? Well, despite the fact that developing applications without frameworks is not a good idea, I can say that we will encounter the same problems: data (and state) are not saved between different HTTP requests. This is a fundamental characteristic of HTTP. This can be avoided by using caching mechanisms. We saved some SQL queries using aspects. Instead of passing one select query to retrieve the data, and three group by queries with corresponding where clauses, we ran just one where query, and used PINQ to get the aggregated information. ConclusionIn this part, we implemented the ability to facet search a collection of books. As I said, this is just a small example, which has room for improvement, and which has a number of limitations. In this article (webmaster level - advanced) we will talk about the so-called intersecting in different ways. "faceted" navigation. To simplify the assimilation of the material, I recommend going through the Wikipedia article “Facet classification” and publications on English language(but with pictures!) "Design better faceted navigation for your websites." Faceted navigation with filtering by color or price range may be useful for your visitors, but is often harmful in search due to the fact that it creates many combinations of addresses with duplicate content. Due to duplicates search engines will not be able to quickly scan the site for content updates, which consequently affects indexing. To minimize this problem and help webmasters make faceted navigation search friendly, we'd like to: Ideal for users and Google search Clear path to products/article pages: URL representation for the category page: Product specific URL representation: Unwanted duplicates caused by faceted navigation The same page is accessible from different web addresses: Canonical page URL: example.com/product.php? item=swedish-fish Duplicate page URL:example.com/product.php? item=swedish-fish&category=gummy-candies&price=5-10 category=gummy-candies&taste=sour&price=5-10 Errors:
URL: example.com/category.php? category=gummy-candies&taste=sour&price=over-10 Errors:
Example No. 1: Non-standard parameters are used as part of the URL: commas and parentheses, instead key=value&:
example.com/category?category=gummy-candy&sort=low-to-high&sid=789 Example #2: Using directories or file paths rather than parameters in lists of values that do not change the content of the page: Good decision:
Common values that do not change the content of the page and must be listed as URL parameters include:
Using minor data generated by site users (such as longitude/latitude or "days ago") in crawled and indexed addresses:
User-agent: * Disallow: /filtering/ Example No. 4. Adding URL parameters without logic.
Example #5: Suggest further refinements (filtering) when there are null results. Badly: Clarification to a page with zero results (for example, price=over-10), which frustrates users and causes unnecessary requests for search engines. How to: Displaying a page with zero results (for example, price=over-10) is not allowed, plus users are prohibited from making unnecessary clicks, and search engines are prohibited from crawling this not useful page. It is necessary to prevent the occurrence unnecessary addresses and minimize space for the visitor by creating URLs only when products are available. This will help keep users engaged on your site (fewer clicks on the back button when no products are found) and will reduce the number of possible URLs known to search engines. Additionally, if a page is not just "temporarily out of stock" but is unlikely to ever contain relevant information, you might want to consider giving it a 404 response code. On your 404 page, you can design a helpful message for users with more options in the navigation, or a search box so users can find related products. For new sites whose webmasters are considering implementing faceted navigation, there are several options for optimizing the crawling (the set of addresses on your site known to Google) of unique content pages and reducing duplicate pages from getting into the search engine index (consolidation of indexing signals). Determine what URL parameters are required for search engines to crawl each individual content page (that is, determine what parameters are needed to create at least one click path to each item). Required parameters may include item-id , category-id , page etc. Determine which parameters will be useful to visitors with their queries, and which are likely to cause duplication in crawling and indexing. In the confectionery example (marmalade), the URL parameter "taste" could be valuable for users with queries in the example taste=sour. However, it is logical to consider the "price" parameter to cause unnecessary duplication of category=gummy-candies&taste=sour&price=over-10 . Other common examples:
Option 1: and internal links Mark all unnecessary URLs with the . This will reduce the search robot's labor costs and prevent a decrease in crawling frequency. You need to manage scanning globally through robots.txt (Translator's note: see article " "). Option 2: Robots.txt and Disallow URLs with unnecessary parameters are included in the /filtering/ directory, which will be closed in robots.txt (disallow). This will allow all search engines to crawl only the “correct” in-link (content) of the site, but will block crawling of unwanted URLs at once. For example (example.com/category.php?category=gummy-candies), if the valuable parameters were item, category and taste, and the session ID and price were unnecessary, then the URL for taste would be like this: Option 3: Separate Hosts Make sure that best solutions, listed above (for example, for unnecessary addresses) still apply. Otherwise, search engines have already formed a large link mass in the index. Thus, your work will be aimed at reducing further growth of unnecessary pages crawled by Googlebot and consolidating indexing signals. Use parameters with standard encoding and key=value format. Make sure that values that do not change page content, such as session IDs, are implemented as key=value rather than directories. Don't allow clicks or generate URLs when there are no elements to filter. Add logic to URL parameter mapping: remove unnecessary parameters rather than constantly adding values (e.g. avoid link generation like this: example.com/product?cat=gummy-candy&cat=lollipops &cat=gummy-candy&item=swedish-fish ). Store valuable parameters in URLs by listing them first (since URLs are visible in search results) and less relevant parameters last (for example, session ID). Make sure that when using JavaScript for dynamic content management (sort/filter/hide) without updating the URL, there are real web addresses on your site that have search value, such as main categories and product pages, that are crawlable and indexable. Try not to use only home page(i.e. one URL) for your entire site, and using JavaScript to dynamically change the content with navigation - this, unfortunately, will give users only one URL in searches. Also, check that there is no performance impact on dynamic filtering in the worst side, as it will prevent the user from working with the site. Improve indexing various pages one content by specifying the rel="canonical" attribute to the privileged version of the page. The rel="canonical" attribute can be used within one or more domains. Optimize the indexing of content paginated (for example, page=1 and page=2 from the "gummy candies" category) by either:
Read also
|