⚠️ This instruction is appropriate for App Lib V2 only. Please contact us for further assistance.
Please read: App Lib v2 Structure before reading this.
Overview
We do customization in boost-pfs-filter.js or boost-pfs-instant-search.js files by overriding components functions.
In the function we override, this
points to the component, and you can use this
to access the data of that component.
There are 3 ways to override functions:
Override before/after functions
Override special functions
Override other functions
⚠️ Custom code need to be written with ES5 syntax.
Override before/after function
These functions are on all components, they're all empty functions that you can override:
beforeInit, afterInit, beforeRender, afterRender, beforeBindEvents, afterBindEvents.
Recommend using before/after function instead of init, render, and bindEvents itself.
When to override which function:
before/afterInit:
To create new component (example:
this.customButton = new ApplyButton()
)To change child's component position → see section 2.1.1
before/afterRender:
To render new component (example:
this.$element.append(this.customButton.$element
)To add/remove css classes
before/afterBindEvents:
To add extra events
To remove events (example:
jQ(this.$element).off('click')
)
Example: Add a css class to filter options
FilterOption.prototype.afterRender = function() {
// "this" refers to the filter option component
// Add a custom CSS class to the element
this.$element.addClass('my-custom-css-class');
};
Example: Disable click event on filter option item
FilterOptionItem.prototype.afterBindEvents = function() {
// Unbind click events from the element to prevent any default or previously set actions
this.$element.off('click');
};
Example: Display number of selected filter option item
// Called after the filter tree is rendered
FilterTree.prototype.afterRender = function() {
var count = 0;
// Sum up the number of applied filter items from each filter option
this.filterOptions.forEach(function(filterOption) {
count += filterOption.numberAppliedFilterItems;
});
// Prepend the count of selected filters above the filter tree
// Ensure to replace '' with the correct selector where you want to prepend the information
jQ('.target-element-selector').prepend('<div>Selected filters: ' + count + '</div>');
};
⚠️ To quickly list all available fields/functions for each component, type boostPFS
in the browser console for the whole component tree.
Override other functions in app lib
Somtimes the before/after functions and special functions are not enough.
In these cases, we override function from app lib directly.
⚠️ In app lib v1, we copy the whole function out, but for app lib v2, please use this new way
var originalFunction = Component.prototype.functionName;
Component.prototype.functionName = function(param1, param2) {
// Call the original function with the current context and provided parameters
originalFunction.call(this, param1, param2);
// Add your custom code here, after the original function has run
// Example custom logic
};
Example: Read Modify other function without before/after.
App lib customization
Commonly asked custom elements (clear all, apply all,...)
Commonly requested elements are placed in the filter tree, but display none by default.
We have a header and footer sections on filter tree, to store these elements. It's like a toolbar to get custom elements.
On filter tree (both vertical and horizontal), there are header/footer sections with:
Apply all button (in footer)
Clear all button (in header)
Hide filter button (in header)
On filter option (both vertical and horizontal):
Number of selected filter items (on title)
Can also be accessed in filterOption.numberAppliedFilterItems
Apply button (on each filter option)
On refine by:
A setting to separate refine by from the filter tree, and display it as vertical/horizontal.
Change element's position
The parent component appends the child component to itself.
Example: In our app lib the FilterTree component render the ApplyButton on the footer
// Assuming original code snippet
FilterTree.prototype.render = function() {
// Existing rendering logic...
this.$element.find(this.selector.filterFooter).append(this.applyAllButton.$element);
// Insert custom code here to extend or modify behavior after rendering
// For example, adding a custom class or additional button
this.$element.find(this.selector.filterFooter).addClass('custom-class');
this.$element.find(this.selector.filterFooter).append(this.customButton.$element);
};
We want to change the apply all button to the header: override the afterRender function
// Overridden method in boost-pfs-filter.js
FilterTree.prototype.afterRender = function() {
// Append the applyAllButton to the filterHeader
this.$element.find(this.selector.filterHeader).append(this.applyAllButton.$element);
// Example of additional custom code
// You could add custom logic here, such as additional buttons or informational elements
// For instance, adding a reset filters button next to the apply all button
var resetButton = $('<button>').text('Reset Filters').addClass('reset-filters-btn');
this.$element.find(this.selector.filterHeader).append(resetButton);
};
⇒ We changed the element's position from footer to header.
Example: Place the apply all button outside of filter tree:
// Overridden function in boost-pfs-filter.js
FilterTree.prototype.afterRender = function() {
// Append the applyAllButton to a custom container identified by 'custom-container'
jQ('#custom-container').append(this.applyAllButton.$element);
};
Demo: Vertical Layout - Expand filter - Overlay
Render extra elements
Simple element
Example: render a close button on the footer
The footer is part of the filter tree, we override FilterTree's afterRender
// Override the afterRender function to append a custom button in the filter footer
FilterTree.prototype.afterRender = function() {
// Append a custom 'Close' button to the filter footer
this.$element.find(this.selector.filterFooter).append('<div class="custom-button">Close</div>');
};
// Bind events to the new custom button for closing the filter tree
FilterTree.prototype.afterBindEvents = function() {
// Attach a click event handler to the custom button, binding it to the onCloseFilterTree function
this.$element.find('.custom-button').on('click', this.onCloseFilterTree.bind(this));
// Note: this.onCloseFilterTree can be replaced with any other function as needed
};
Component element
Example: We want 2 apply all button, on header and on footer. Currently app lib has 1 apply all button on footer. Need to add another on header.
// Override the afterInit method to create a second apply button
FilterTree.prototype.afterInit = function() {
// Create a second apply button for the filter tree
this.applyButton2 = new ApplyButton(this.filterTreeType, 'apply-all');
// Add the newly created apply button to the filter tree components
this.addComponent(this.applyButton2);
};
// Override the afterRender method to append the second apply button to the filter header
FilterTree.prototype.afterRender = function() {
// Append the second apply button to the filter header
this.$element.find(this.selector.filterHeader).append(this.applyButton2.$element);
};
Modify events
We use the afterBindEvents
function, to unbind original event, and insert our own events
Example: On filter by multi-level tag, customize for the first level acts like a label for toggling, not like a filterable tag
FilterOptionItemMultiLevelTag.prototype.afterBindEvents = function() {
// If this is the first level item
if (this.level === 1) {
// Remove any existing click event handlers
this.$element.off('click');
// Bind a new click event handler with a custom function
this.$element.on('click', yourCustomFunction.bind(this));
}
};
// Define a custom function to toggle the 'open' class on ul elements within this item
function yourCustomFunction() {
this.$element.find('ul').toggleClass('open');
}
Modify other function without before/after
We sometimes need to override functions outside of render/bindEvent. These functions don't have a before or after function.
We don't want to copy a big function out like in app lib v1. So we will do this:
Example: Capitalize first letter of filter item label, except for 'iPhone' and 'iPad'.
⇒ Need to change the buildLabel function
// Save the original buildLabel function
var originalBuildLabelFunction = FilterOptionItem.prototype.buildLabel;
// Override the buildLabel function
FilterOptionItem.prototype.buildLabel = function() {
// Call the original function
var label = originalBuildLabelFunction.call(this);
// Custom modifications to the label
if (label === 'Iphone') {
label = 'iPhone';
}
if (label === 'Ipad') {
label = 'iPad';
}
// Return the potentially modified label
return label;
};
Commonly customized functions
Show/hide filter tree
Click a button outside the filter, to show/hide filter
Click a button inside the filter, to hide filter
The show/hide button is the mobile button, change css to show on desktop.
Function to handle clicking mobile button:
FilterMobileButton.prototype.onClickMobileButton
Function to handle clicking the close button inside filter tree:
FilterTree.prototype.onCloseFilterTree
Display refine by separately
Enable settings in boost-pfs-filter.js
var boostPFSFilterConfig = { general: { separateRefineByFromFilter: true }}
Place this div wherever you want to render refine by
Vertical refine by
<div class='boost-pfs-filter-refine-by-wrapper-v'></div>
Horizontal refine by
<div class='boost-pfs-filter-refine-by-wrapper-h'></div>
Customize HTML template (Advanced)
Override the functions
getTemplate
,compileTemplate
,render
Get template returns the raw template string, before replacing anything
Example: FilterTree.getTemplate()
returns a string:
<div class="boost-pfs-filter-tree-content" aria-live="polite" role="navigation" aria-label="{{label.productFilter}}">
{{header}}
<div class="{{class.filterRefineByWrapper}}">
{{refineBy}}
</div>
<div class="{{class.filterOptionsWrapper}}">
{{filterOptions}}
</div>
{{footer}}
</div>
⚠️ Important
You can change the html by overriding
getTemplate
, with the condition that the inrender
function, children element must be appended correctly.In the above example, {{refineBy}} and {{filterOptions}} are components
The string {{refineBy}} and {{filterOptions}} will be replaced with empty string. Showing it in the template is only for code-reading purpose.
refineBy and filterOption elements will be appended to the parent element in the render function,
// In the compileTemplate function of FilterTree
FilterTree.prototype.compileTemplate = function() {
// Get the raw template
var template = this.getTemplate();
// Replace the 'refineBy' placeholder with an empty string
// Ensure this placeholder syntax aligns with how your templating system works
template = template.replace(/\{\{refineBy\}\}/g, '');
return template;
};
// In the render function of FilterTree
FilterTree.prototype.render = function() {
// Create a new jQuery element from HTML string in compileTemplate
this.$element = jQuery(this.compileTemplate());
// Append the refineBy element, based on selector
// Ensure that this.refineBy.$element is properly instantiated before appending
if (this.refineBy && this.refineBy.$element) {
this.$element.find(this.selector.refineByContainer).append(this.refineBy.$element);
}
};
Why we do this: each element is its own object with events, data, functions... and is not a string. So we can't replace {{refineBy}} with a html string like in app lib v1.
Access data returned from API
A copy of the original data returned from API is kept in boostPFS.filter.data. You can access this field anytime anywhere.
Customize filter value sorting
Function to use:
FilterOption.prototype.sortValues(values)
This function need to modify the values
array in-place.
Example: sort the size values by your custom rules
var originalSortFn = FilterOption.prototype.sortValues;
FilterOption.prototype.sortValues = function(values) {
// If this is not the 'size' filter option, use the original sorting function
if (this.filterOptionId !== 'pf_otp_size') {
originalSortFn.call(this, values);
} else {
// Define custom order for 'size'
var sortingArr = ['S', 'M', 'L'];
// Custom sort function for size, reorders 'values' in-place
values.sort(function(a, b) {
return sortingArr.indexOf(a) - sortingArr.indexOf(b);
});
}
// No return needed as sorting is done in-place
};
Theme customization
Functions to override for theme elements
Override compileTemplate
function to return HTML string
The bindEvents
function is optional.
Important: Use this.$element
, this.data
,... to access the component's data for rendering.
Product Items:
// For a standard grid view item
ProductGridItem.prototype.compileTemplate = function() {
// Custom code to compile the template for a grid item
};
// For a list view item
ProductListItem.prototype.compileTemplate = function() {
// Custom code to compile the template for a list item
};
// For a collage view item
ProductCollageItem.prototype.compileTemplate = function() {
// Custom code to compile the template for a collage item
};
Pagination
ProductPaginationDefault.prototype.compileTemplate()ProductPaginationDefault.prototype.bindEvents()
Other elements:
Breadcrumb.prototype.compileTemplate() ProductDisplayType.prototype.compileTemplate() ProductDisplayType.prototype.bindEvents() ProductLimit.prototype.compileTemplate()ProductLimit.prototype.bindEvents() ProductSorting.prototype.compileTemplate()ProductSorting.prototype.bindEvents()
Functions for extra elements
buildExtraProductList
in app lib v1, is in app lib v2:
// This function is called whenever re-render product list (won't call on first load)ProductList.prototype.afterRender = function(){};
buildAdditionalElements
in app lib v2, is in app lib v2:
// This function is like the above function, but is called on first loadFilter.prototype.afterRender = function(){};
Third-party library customization
Rangeslider
We use noUISlider for range slider.
Function to override to change slider config:
FilterOptionRangeSlider.prototype.getSliderConfig()
Returns a config object that matches this documentation: https://refreshless.com/nouislider/slider-options/
Scrollbar
We only use browser's CSS to style scrollbar.
However, if you want to customize scrollbar styles, we recommend SimpleBar. It works with any browser native scrollbar.
If you have any questions or need further assistance, please do not hesitate to contact our dedicated support team at [email protected].