⚠️ 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.