For dev - App Lib v2 Customization
In this article
- 1. Overview
- 1.1. Override before/after function
- 1.2. Override other functions in app lib
- 2. App lib customization
- 2.1. Commonly asked custom elements (clear all, apply all,...)
- 2.1.1. Change element's position
- 2.2. Render extra elements
- 2.2.1. Simple element
- 2.2.2. Component element
- 2.3. Modify events
- 2.4. Modify other function without before/after
- 2.5. Commonly customized functions
- 2.5.1. Show/hide filter tree
- 2.5.2. Display refine by separately
- 2.6. Customize HTML template (Advanced)
- 3. Access data returned from API
- 3.1. Customize filter value sorting
- 4. Theme customization
- 4.1. Functions to override for theme elements
- 4.2. Functions for extra elements
- 5. Third-party library customization
- 5.1. Rangeslider
- 5.2. Scrollbar
A guide for customizing our app lib.
Important: Please read: App Lib v2 Structure before reading this.
1. Overview
We do customization in boost-pfs-filter.js
or boost-pfs-instant-search.js
file, 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
Important: Custom code need to be written with ES5 syntax.
1.1. 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
- To create new component (example:
- before/afterRender:
- To render new component (example:
this.$element.append(this.customButton.$element
) - To add/remove css classes
- To render new component (example:
- 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" points to the filter option component this.$element.addClass('my-custom-css-class'); }
Example: Disable click event on filter option item
FilterOptionItem.prototype.afterBindEvents = function() { this.$element.off('click'); }
Example: Display number of selected filter option item
// We use the afterRender function of FilterTree, it will be called after the filter tree is rendered FilterTree.prototype.afterRender = function() { var count = 0; this.filterOptions.forEach(function(filterOption) { // FilterOption has numberAppliedFilterItems field. We add those up. count += filterOption.numberAppliedFilterItems; }) // We prepend the count number above the filter tree jQ('').prepend('<div> Selected filters: ' + count + '</div>'); }
Important: To quickly list all available fields/functions for each component, type
boostPFS
in the browser console for the whole component tree.
1.2. 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.
Important: 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 in our app lib. // We don't have to copy a very big function out here to modify. // function.call(this, param1, param2) binds the "this" pointer and params to the original function. originalFunction.call(this, param1, param2); // Do your custom code after the original function has run }
Example: See section 2.4.
2. App lib customization
2.1. 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.
2.1.1. 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
// Original code in app lib FilterTree.prototype.render = function() { ... this.$element.find(this.selector.filterFooter).append(this.applyAllButton.$element); ... }
We want to change the apply all button to the header: override the afterRender function
// We override this function in boost-pfs-filter.js FilterTree.prototype.afterRender = function() { this.$element.find(this.selector.filterHeader).append(this.applyAllButton.$element); }
⇒ We changed the element's position from footer to header.
Example: Place the apply all button outside of filter tree:
// We override this function in boost-pfs-filter.js FilterTree.prototype.afterRender = function() { jQ('#custom-container').append(this.applyAllButton.$element); }
Demo: Vertical Layout - Expand filter - Overlay
2.2. Render extra elements
2.2.1. Simple element
Example: render a close button on the footer
The footer is part of the filter tree, we override FilterTree's afterRender
// We override this function in boost-pfs-filter.js FilterTree.prototype.afterRender = function() { // this.$element points to the filter tree on the DOM this.$element .find(this.selector.filterFooter) .append('<div class="custom-button">Close</div>'); } FilterTree.prototype.afterBindEvents = function() { this.$element.find('.custom-button').on('click', this.onCloseFilterTree.bind(this)); // this.onCloseFilterTree can be replaced with a function outside of app lib }
2.2.2. 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.
FilterTree.prototype.afterInit = function() { // creates a 2nd apply button after init this.applyButton2 = new ApplyButton(this.filterTreeType, 'apply-all'); this.addComponent(this.applyButton2); } FilterTree.prototype.afterRender = function() { // append the 2nd apply button to filter header this.$element .find(this.selector.filterHeader) .append(this.applyButton2.$element); }
2.3. 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.level == 1) { this.$element.off('click'); this.$element.on('click', yourCustomFunction.bind(this)); } } function yourCustomFunction() { this.$element.find('ul').toggleClass('open'); }
2.4. 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 function var originalBuildLabelFunction = FilterOptionItem.prototype.buildLabel; FilterOptionItem.prototype.buildLabel = function() { var label = originalBuildLabelFunction.call(this); // Do your custom code after the original function has run if (label == 'Iphone') label = 'iPhone'; if (label == 'Ipad') label = 'iPad'; return label; }
2.5. Commonly customized functions
2.5.1. 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
2.5.2. 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>
2.6. 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 compile template function FilterTree.prototype.compileTemplate = function() { .... // We get the raw template this.getTemplate() // Replace refine by with empty string .replace({{refineBy}}, ''); .... } // In the render function FilterTree.prototype.render = function() { .... // Create new jquery element from HTML string in compile template this.$element = jQ(this.compileTemplate()) // Append the refine by element, based on selector 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.
3. 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.
3.1. 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, call the original sort function if (this.filterOptionId != 'pf_otp_size') { originalSortFn.call(this, values); // Run your custom sort } else { sortingArr = ['S', 'M', 'L']; // This sort function runs in-place, modify the values array directly values.sort(function(a, b){ return sortingArr.indexOf(a) - sortingArr.indexOf(b); }); } // Here we don't need to return anything. }
4. Theme customization
4.1. 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:
ProductGridItem.prototype.compileTemplate() ProductListItem.prototype.compileTemplate() ProductCollageItem.prototype.compileTemplate()
- 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()
4.2. 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 load Filter.prototype.afterRender = function(){};
5. Third-party library customization
5.1. 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/
5.2. 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.
Documentation: https://github.com/Grsmto/simplebar/tree/master/packages/simplebar#1-documentation