Extending Sitecore Page Selector Component in SXA
Hello everyone, I am back with another blogpost. This time we will discuss about one of the Sitecore SXA default OOTB component Page Selector and how we can extend it. We all know that implementing pagination is one of the crucial requirement whenever we have some listing implementation on our website. Listing example can be, showing all related search results when someone search for any text on website.
In SXA, we all know to implement pagination we can go for SXA OOTB Page Selector component. Page Selector component only needs the search signature and in datasource field we give the default data item for showing the pagination component. This component is binded to your Search Result component using the Search Signature to show the result count according to the pagination parameter to paginate define in the Search Result component.
But sometime we have some extra requirement which cannot be achieved using default component. For ex. Like we need to scroll to top of the search result component when user paginates.
To implement this thing, we think about jQuery solution but by default this is not supported by SXA OOTB component as all JS related logic and events are executed in respective component JS file.SXA used BackBone js for its default implementation. Now if jQuery doesn't work then what else can be done and how it can be done?
So for this kind of scenario we have to override existing Component JS file. For example in our case we need to override existing Component-search-page-selector.js. Lets see how we can do it.
To override existing JS file there are two important steps which we need to keep in mind.
1. Understand the existing JS file
2. Finding the exact place where we need to add our custom code
For understanding the existing functionality we can refer Sitecore SXA documentation. Below is the link for the same.
component-search-page-selector
Once we are clear with the above now is the time to add our custom code. For our requirement mentioned above we can see where we can add our custom code.
Create a JS file and add the below code
Once you add your custom code, save it. Then upload this file in your script folder located inside your website Theme folder in sitecore.
Once you add the file and publish, whenever your website is launched, this file will basically override your default component js file. In our case we can add the scrolling logic after the add custom comment code.
Don't change any existing functionality logic.
XA.component.search.pageSelector = (function ($, document) {
var api = {},
queryModel,
initialized = false;
facetName = "e";
var SearchPageSelectorModel = Backbone.Model.extend(
/** @lends module:searchPageSelector.SearchPageSelectorModel.prototype **/
{
defaults: {
dataProperties: {},
resultsCount: 0,
offset: 0,
selectedValue: 1,
pageSize: 0,
repeatRequest: false,
template: "<ul class='page-selector-list'> " +
"<li class='page-selector-item-first'><a href='#'><%= data.first %></a></li>" +
"<li class='page-selector-item-previous'><a href='#'><%= data.previous %></a></li>" +
"<% var beforePage = 0; %>" +
"<% _.each(data.pages, function(page){ %>" +
"<% if((beforePage+1) != page.number){ %>" +
"<li><span class='page-selector-more'>...</span></li>" +
"<% } %>" +
"<% beforePage = page.number; %>" +
"<% if(data.selectedValue === page.number){ %>" +
"<% active = 'active'; %>" +
"<% }else { active = '' } %>" +
"<li><a class='page-selector-item-link <%= active %>'
data-offset='<%= page.offset %>' data-itemNumber='<%= page.number %>'
href='#'><%= page.number %></a></li>" +
"<% }); %>" +
"<li class='page-selector-item-next'><a href='#'><%= data.next %></a></li>" +
"<li class='page-selector-item-last'><a href='#'><%= data.last %></a></li>" +
"</ul>",
sig: [],
timeStamp: new Date().getTime()
}
});
var SearchPageSelectorView = XA.component.search.baseView.extend(
/** @lends module:searchPageSelector.SearchPageSelectorView **/
{
initialize: function () {
var dataProp = this.$el.data();
if (dataProp.properties.searchResultsSignature === null) {
dataProp.properties.searchResultsSignature = "";
}
this.model.set("dataProperties", dataProp);
this.model.set("sig", this.translateSignatures(dataProp.properties.searchResultsSignature, facetName));
this.model.on("change", this.render, this);
XA.component.search.vent.on("results-loaded", this.handleLoadedData.bind(this));
//check whether page is opened from disc - if yes then we are in Creative Exchange mode and lets mock some content
if (window.location.href.startsWith("file://")) {
this.model.set({
"resultsCount": 10,
"pageSize": 2,
"selectedValue": 2
});
}
},
events: {
'click .page-selector-item-link': "updateSelectedValue",
'click .page-selector-item-first a': "showFirstPage",
'click .page-selector-item-last a': "showLastPage",
'click .page-selector-item-previous a': "showPrevPage",
'click .page-selector-item-next a': "showNextPage"
},
updateModelAfterSearch: function (data, selectedValue) {
this.model.set({
"pageSize": parseInt(data.pageSize),
"resultsCount": parseInt(data.dataCount),
"offset": parseInt(data.offset),
"selectedValue": parseInt(selectedValue)
});
this.model.set("timeStamp", new Date().getTime());
this.updateElementCssClass(data);
},
updateElementCssClass: function (data) {
this.el.classList.remove("page-selector-empty")
this.el.classList.remove("page-selector-single-page")
if (data.dataCount === 0) {
this.el.classList.add("page-selector-empty")
} else if (data.pageSize >=; data.dataCount || data.offset > data.dataCount) {
this.el.classList.add("page-selector-single-page")
}
},
updateSelectedValue: function (event) {
event.preventDefault();
var sig = this.model.get("sig"),
dataProp = $(event.target).data();
console.log(dataProp);
queryModel.updateHash(this.updateSignaturesHash(sig, dataProp.offset, {}));
//Add custom code
},
showFirstPage: function (event) {
event.preventDefault();
var sig = this.model.get("sig"),
dataProp = $(event.target).data();
queryModel.updateHash(this.updateSignaturesHash(sig, 0, {}));
//Add custom code
},
showLastPage: function (event) {
event.preventDefault();
var lastPageItems = this.model.get("resultsCount") % this.model.get("pageSize"),
offset = this.model.get("resultsCount") - ((lastPageItems === 0) ? this.model.get("pageSize")
: lastPageItems),
sig = this.model.get("sig");
queryModel.updateHash(this.updateSignaturesHash(sig, offset, {}));
//Add custom code
},
showNextPage: function (event) {
event.preventDefault();
var offset = this.model.get("offset"),
sig = this.model.get("sig");
if ((offset + this.model.get("pageSize")) < this.model.get("resultsCount")) {
offset += this.model.get("pageSize");
}
queryModel.updateHash(this.updateSignaturesHash(sig, offset, {}));
//Add custom code
},
showPrevPage: function (event) {
event.preventDefault();
var offset = this.model.get("offset"),
dataProp = $(event.target).data(),
sig = this.model.get("sig");
if ((offset - this.model.get("pageSize")) >= 0) {
offset -= this.model.get("pageSize");
}
queryModel.updateHash(this.updateSignaturesHash(sig, offset, {}));
//Add custom code
},
render: function () {
var inst = this,
dataProp = this.model.get("dataProperties").properties,
resultsCount = this.model.get("resultsCount"),
pageSize = this.model.get("pageSize"),
selectedValue = this.model.get("selectedValue"),
pagesCount = Math.ceil(resultsCount / pageSize),
pages = [],
rangeStart = selectedValue - dataProp.treshold / 2,
rangeEnd = selectedValue + dataProp.treshold / 2,
templateObj;
if (rangeStart < 0) {
rangeEnd += Math.abs(rangeStart);
}
if (rangeEnd > pagesCount) {
rangeStart -= (rangeEnd - pagesCount);
}
if (dataProp.treshold >= pagesCount) {
for (var i = 0; i < pagesCount; i++) {
pages.push({ number: i + 1, offset: i * pageSize });
}
}
else {
for (var i = 1; i <= pagesCount; i++) {
if ((i === 1) || (i === pagesCount)) {
pages.push({ number: i, offset: (i - 1) * pageSize });
}
else if ((i >= rangeStart) && (i <= rangeEnd)) {
pages.push({ number: i, offset: (i - 1) * pageSize });
}
}
}
templateObj = {
previous: dataProp.previous,
first: dataProp.first,
next: dataProp.next,
last: dataProp.last,
pages: pages,
selectedValue: selectedValue
};
var template = _.template(inst.model.get("template"));
var templateResult = template({ data: templateObj });
this.$el.html(templateResult);
this.handleButtonState(selectedValue, pagesCount);
},
handleButtonState: function (selectedPage, pageCount) {
this.$el.find(".page-selector-item-last, .page-selector-item-next").removeClass("inactive");
this.$el.find(".page-selector-item-first, .page-selector-item-previous").removeClass("inactive");
if (pageCount == 0) {
this.$el.find(".page-selector-item-first, .page-selector-item-previous").addClass("inactive");
this.$el.find(".page-selector-item-last, .page-selector-item-next").addClass("inactive");
}
else {
if (selectedPage == 1) {
this.$el.find(".page-selector-item-first, .page-selector-item-previous").addClass("inactive");
}
if (selectedPage == pageCount) {
this.$el.find(".page-selector-item-last, .page-selector-item-next").addClass("inactive");
}
}
},
handleLoadedData: function (data) {
var that = this,
sig = this.model.get("dataProperties").properties.searchResultsSignature.split(','),
hash = queryModel.parseHashParameters(window.location.hash),
newSelectedValue,
param,
hashObj,
i;
if (typeof data.offset === 'undefined') {
data.offset = 0;
}
for (i = 0; i < sig.length; i++) {
if (encodeURIComponent(sig[i]) === data.searchResultsSignature) {
if (data.pageSize > data.dataCount || data.offset > data.dataCount) {
//When we have less results then page size then show first page
this.updateModelAfterSearch(data, 1);
//Wait till all requests are done and go to first page
setTimeout(function () {
//queryModel.updateHash({e: 0});
//- this will work, but doesn't give us proper browser history
hashObj = queryModel.parseHashParameters(window.location.hash);
param = data.searchResultsSignature !== "" ? data.searchResultsSignature + "_e" : "e";
if (hash[param] !== "0") {
hashObj[param] = 0;
Backbone.history.
navigate(that.createFirstPageUrlHash(hashObj), { trigger: true, replace: true });
}
}, 100);
} else {
newSelectedValue = Math.ceil(data.offset / data.pageSize) + 1;
this.updateModelAfterSearch(data, newSelectedValue);
}
}
}
},
createFirstPageUrlHash: function (hashObj) {
var hashStr = "";
var i = 0;
_.each(hashObj, function (item, key) {
if (i > 0) {
hashStr += "&";
}
i++;
hashStr += key + "=" + item;
});
return hashStr;
}
});
api.init = function () {
if ($("body").hasClass("on-page-editor") || initialized) {
return;
}
queryModel = XA.component.search.query;
var searchPageSelector = $(".page-selector");
_.each(searchPageSelector, function (elem) {
new SearchPageSelectorView({ el: $(elem), model: new SearchPageSelectorModel });
});
initialized = true;
};
return api;
}(jQuery, document));
XA.register('customsearchPageSelector', XA.component.search.pageSelector);
Thanks for reading.
Comments
Post a Comment