Skip to main content
For dev - App Lib V2 Customization

A guide for customizing our app lib V2

Thomas Ta avatar
Written by Thomas Ta
Updated over a week ago

⚠️ 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
};


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 in render 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].

Did this answer your question?