feat(main): Add base theme: This is the falcon theme out of the box.
This is falcon v3.1.2
This commit is contained in:
54
falcon/_dev/js/listing/components/filters/Filters.js
Normal file
54
falcon/_dev/js/listing/components/filters/Filters.js
Normal file
@ -0,0 +1,54 @@
|
||||
import prestashop from 'prestashop';
|
||||
import $ from 'jquery';
|
||||
import FiltersRangeSliders from '@js/listing/components/filters/FiltersRangeSliders';
|
||||
|
||||
class Filters {
|
||||
constructor() {
|
||||
this.$body = $('body');
|
||||
this.setEvents();
|
||||
this.rangeSliders = FiltersRangeSliders;
|
||||
this.rangeSliders.init();
|
||||
}
|
||||
|
||||
setEvents() {
|
||||
prestashop.on('updatedProductList', () => {
|
||||
prestashop.pageLoader.hideLoader();
|
||||
this.rangeSliders.init();
|
||||
});
|
||||
|
||||
prestashop.on('updateFacets', () => {
|
||||
prestashop.pageLoader.showLoader();
|
||||
});
|
||||
|
||||
this.$body.on('click', '.js-search-link', (event) => {
|
||||
event.preventDefault();
|
||||
prestashop.emit('updateFacets', $(event.target).closest('a').get(0).href);
|
||||
});
|
||||
|
||||
this.$body.on('change', '[data-action="search-select"]', ({ target }) => {
|
||||
prestashop.emit('updateFacets', $(target).find('option:selected').data('href'));
|
||||
});
|
||||
|
||||
this.$body.on('click', '.js-search-filters-clear-all', (event) => {
|
||||
prestashop.emit('updateFacets', this.constructor.parseSearchUrl(event));
|
||||
});
|
||||
|
||||
this.$body.on('change', '#search_filters input[data-search-url]', (event) => {
|
||||
prestashop.emit('updateFacets', this.constructor.parseSearchUrl(event));
|
||||
});
|
||||
}
|
||||
|
||||
static parseSearchUrl(event) {
|
||||
if (event.target.dataset.searchUrl !== undefined) {
|
||||
return event.target.dataset.searchUrl;
|
||||
}
|
||||
|
||||
if ($(event.target).parent()[0].dataset.searchUrl === undefined) {
|
||||
throw new Error('Can not parse search URL');
|
||||
}
|
||||
|
||||
return $(event.target).parent()[0].dataset.searchUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export default Filters;
|
||||
@ -0,0 +1,15 @@
|
||||
import $ from 'jquery';
|
||||
import RangeSlider from '@js/listing/components/filters/RangeSlider';
|
||||
|
||||
class FiltersRangeSliders {
|
||||
static init() {
|
||||
const $rangeSliders = $('.js-range-slider');
|
||||
|
||||
$rangeSliders.each((i, el) => {
|
||||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "slider" }] */
|
||||
const slider = new RangeSlider(el);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default FiltersRangeSliders;
|
||||
115
falcon/_dev/js/listing/components/filters/FiltersUrlHandler.js
Normal file
115
falcon/_dev/js/listing/components/filters/FiltersUrlHandler.js
Normal file
@ -0,0 +1,115 @@
|
||||
class FiltersUrlHandler {
|
||||
constructor() {
|
||||
this.baseUrl = window.location.origin + window.location.pathname;
|
||||
this.oldSearchUrl = null;
|
||||
this.searchUrl = null;
|
||||
}
|
||||
|
||||
setOldSearchUrl() {
|
||||
this.oldSearchUrl = this.searchUrl;
|
||||
}
|
||||
|
||||
getFiltersUrl() {
|
||||
this.setOldSearchUrl();
|
||||
return `${this.baseUrl}?q=${this.searchUrl}`;
|
||||
}
|
||||
|
||||
setSearchUrl() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
this.searchUrl = searchParams.get('q');
|
||||
this.oldSearchUrl = searchParams.get('q');
|
||||
}
|
||||
|
||||
setRangeParams(group, { unit, from, to }) {
|
||||
this.removeGroup(group);
|
||||
|
||||
this.appendParam(group, unit);
|
||||
this.appendParam(group, from);
|
||||
this.appendParam(group, to);
|
||||
}
|
||||
|
||||
appendParam(group, prop) {
|
||||
const oldSearchUrl = this.searchUrl || '';
|
||||
let newSearchUrl = oldSearchUrl.length ? oldSearchUrl.split('/') : [];
|
||||
let groupExist = false;
|
||||
const newSearchUrlLength = newSearchUrl.length;
|
||||
group = FiltersUrlHandler.specialEncode(group);
|
||||
prop = FiltersUrlHandler.specialEncode(prop);
|
||||
|
||||
for (let i = 0; i < newSearchUrlLength; i += 1) {
|
||||
const filterGroup = newSearchUrl[i];
|
||||
const filterGroupArray = filterGroup.split('-');
|
||||
|
||||
if (filterGroupArray[0] === group) {
|
||||
newSearchUrl[i] = `${newSearchUrl[i]}-${prop}`;
|
||||
groupExist = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!groupExist) {
|
||||
newSearchUrl = [...newSearchUrl, `${group}-${prop}`];
|
||||
}
|
||||
|
||||
this.searchUrl = FiltersUrlHandler.specialDecode(FiltersUrlHandler.formatSearchUrl(newSearchUrl));
|
||||
}
|
||||
|
||||
removeGroup(group) {
|
||||
const oldSearchUrl = this.searchUrl || '';
|
||||
const newSearchUrl = oldSearchUrl.length ? oldSearchUrl.split('/') : [];
|
||||
const newSearchUrlLength = newSearchUrl.length;
|
||||
|
||||
for (let i = 0; i < newSearchUrlLength; i += 1) {
|
||||
const filterGroup = newSearchUrl[i];
|
||||
const filterGroupArray = filterGroup.split('-');
|
||||
|
||||
if (filterGroupArray[0] === group) {
|
||||
newSearchUrl.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.searchUrl = FiltersUrlHandler.specialDecode(FiltersUrlHandler.formatSearchUrl(newSearchUrl));
|
||||
}
|
||||
|
||||
static toString(value) {
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
static specialEncode(str) {
|
||||
return FiltersUrlHandler.toString(str).replace('/', '[slash]');
|
||||
}
|
||||
|
||||
static specialDecode(str) {
|
||||
return FiltersUrlHandler.toString(str).replace('[slash]', '/');
|
||||
}
|
||||
|
||||
removeParam(group, prop) {
|
||||
const oldSearchUrl = this.searchUrl || '';
|
||||
const newSearchUrl = oldSearchUrl.length ? oldSearchUrl.split('/') : [];
|
||||
const newSearchUrlLength = newSearchUrl.length;
|
||||
|
||||
for (let i = 0; i < newSearchUrlLength; i += 1) {
|
||||
const filterGroup = newSearchUrl[i];
|
||||
const filterGroupArray = filterGroup.split('-');
|
||||
|
||||
if (filterGroupArray[0] === group) {
|
||||
const filterResult = filterGroupArray.filter((el) => el !== prop);
|
||||
|
||||
if (filterResult.length === 1) {
|
||||
newSearchUrl.splice(i, 1);
|
||||
} else {
|
||||
newSearchUrl[i] = filterResult.join('-');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.searchUrl = FiltersUrlHandler.specialDecode(FiltersUrlHandler.formatSearchUrl(newSearchUrl));
|
||||
}
|
||||
|
||||
static formatSearchUrl(array) {
|
||||
return array.join('/');
|
||||
}
|
||||
}
|
||||
|
||||
export default FiltersUrlHandler;
|
||||
182
falcon/_dev/js/listing/components/filters/RangeSlider.js
Normal file
182
falcon/_dev/js/listing/components/filters/RangeSlider.js
Normal file
@ -0,0 +1,182 @@
|
||||
import $ from 'jquery';
|
||||
import prestashop from 'prestashop';
|
||||
import noUiSlider from 'nouislider';
|
||||
import wNumb from 'wnumb';
|
||||
import FiltersUrlHandler from '@js/listing/components/filters/FiltersUrlHandler';
|
||||
|
||||
class RangeSlider {
|
||||
constructor(element) {
|
||||
this.$slider = $(element);
|
||||
|
||||
this.setConfig();
|
||||
this.setFormat();
|
||||
|
||||
this.initFilersSlider();
|
||||
|
||||
this.setEvents();
|
||||
}
|
||||
|
||||
getSliderType() {
|
||||
this.sliderType = this.$slider.data('slider-specifications') ? 'price' : 'weight';
|
||||
}
|
||||
|
||||
setConfig() {
|
||||
this.min = this.$slider.data('slider-min');
|
||||
this.max = this.$slider.data('slider-max');
|
||||
this.$parentContainer = this.$slider.closest('.js-input-range-slider-container');
|
||||
this.$inputs = [this.$parentContainer.find('[data-action="range-from"]'), this.$parentContainer.find('[data-action="range-to"]')];
|
||||
|
||||
this.getSliderType();
|
||||
|
||||
if (this.sliderType === 'price') {
|
||||
const {
|
||||
currencySymbol,
|
||||
positivePattern,
|
||||
} = this.$slider.data('slider-specifications');
|
||||
|
||||
this.sign = currencySymbol;
|
||||
this.positivePattern = positivePattern;
|
||||
this.values = this.$slider.data('slider-values');
|
||||
this.signPosition = this.positivePattern.indexOf('¤') === 0 ? 'prefix' : 'suffix';
|
||||
} else if (this.sliderType === 'weight') {
|
||||
const unit = this.$slider.data('slider-unit');
|
||||
|
||||
this.sign = unit;
|
||||
this.values = this.$slider.data('slider-values');
|
||||
this.signPosition = 'suffix';
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.values)) {
|
||||
this.values = [this.min, this.max];
|
||||
}
|
||||
}
|
||||
|
||||
setFormat() {
|
||||
this.format = wNumb({
|
||||
mark: ',',
|
||||
thousand: ' ',
|
||||
decimals: 0,
|
||||
[this.signPosition]:
|
||||
this.signPosition === 'prefix' ? this.sign : ` ${this.sign}`,
|
||||
});
|
||||
}
|
||||
|
||||
initFilersSlider() {
|
||||
this.sliderHandler = noUiSlider.create(this.$slider.get(0), {
|
||||
start: this.values,
|
||||
connect: [false, true, false],
|
||||
range: {
|
||||
min: this.min,
|
||||
max: this.max,
|
||||
},
|
||||
format: this.format,
|
||||
});
|
||||
}
|
||||
|
||||
initFilersSliderInputs() {
|
||||
this.setInputValues(this.values, true);
|
||||
}
|
||||
|
||||
setInputValues(values, formatValue = false) {
|
||||
this.$inputs.forEach((input, i) => {
|
||||
const val = formatValue ? this.format.from(values[i]) : values[i];
|
||||
$(input).val(val);
|
||||
});
|
||||
}
|
||||
|
||||
setEvents() {
|
||||
this.sliderHandler.off('set', this.constructor.handlerSliderSet);
|
||||
this.sliderHandler.on('set', this.constructor.handlerSliderSet);
|
||||
this.sliderHandler.off('update', this.handlerSliderUpdate);
|
||||
this.sliderHandler.on('update', this.handlerSliderUpdate);
|
||||
|
||||
this.$inputs.forEach(($input) => {
|
||||
$input.off('focus', this.handleInputFocus);
|
||||
$input.on('focus', this.handleInputFocus);
|
||||
$input.off('blur', this.handleInputBlur);
|
||||
$input.on('blur', this.handleInputBlur);
|
||||
$input.on('keyup', this.handleInputKeyup);
|
||||
});
|
||||
}
|
||||
|
||||
static getInputAction($input) {
|
||||
return $input.data('action');
|
||||
}
|
||||
|
||||
getInputPositionInValue($input) {
|
||||
const actionPosition = {
|
||||
'range-from': 0,
|
||||
'range-to': 1,
|
||||
};
|
||||
|
||||
return actionPosition[this.constructor.getInputAction($input)];
|
||||
}
|
||||
|
||||
handleInputFocus = ({ target }) => {
|
||||
const $input = $(target);
|
||||
$input.val(this.format.from($input.val()));
|
||||
};
|
||||
|
||||
handleInputBlur = ({ target }) => {
|
||||
const $input = $(target);
|
||||
const value = $input.val();
|
||||
const position = this.getInputPositionInValue($input);
|
||||
const oldValues = this.values;
|
||||
const newValues = [...oldValues];
|
||||
newValues[position] = value;
|
||||
|
||||
if (value !== oldValues[position]) {
|
||||
this.sliderHandler.set(newValues);
|
||||
} else {
|
||||
$input.val(this.format.to(parseFloat($input.val(), 10)));
|
||||
}
|
||||
};
|
||||
|
||||
handleInputKeyup = ({ target, keyCode }) => {
|
||||
if (keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
const $input = $(target);
|
||||
const value = $input.val();
|
||||
const position = this.getInputPositionInValue($input);
|
||||
const oldValues = this.values;
|
||||
const newValues = [...oldValues];
|
||||
newValues[position] = value;
|
||||
|
||||
if (value !== oldValues[position]) {
|
||||
this.sliderHandler.set(newValues);
|
||||
} else {
|
||||
$input.val(this.format.to(parseFloat($input.val(), 10)));
|
||||
}
|
||||
};
|
||||
|
||||
handlerSliderUpdate = (
|
||||
values,
|
||||
) => {
|
||||
this.setInputValues(values);
|
||||
};
|
||||
|
||||
static handlerSliderSet(
|
||||
values,
|
||||
handle,
|
||||
unencoded,
|
||||
tap,
|
||||
positions,
|
||||
noUiSliderInstance,
|
||||
) {
|
||||
const formatFunction = noUiSliderInstance.options.format;
|
||||
const $target = $(noUiSliderInstance.target);
|
||||
const group = $target.data('slider-label');
|
||||
const unit = $target.data('slider-unit');
|
||||
const [from, to] = values.map((val) => formatFunction.from(val));
|
||||
|
||||
const filtersHandler = new FiltersUrlHandler();
|
||||
filtersHandler.setSearchUrl();
|
||||
filtersHandler.setRangeParams(group, { unit, from, to });
|
||||
|
||||
const newUrl = filtersHandler.getFiltersUrl();
|
||||
prestashop.emit('updateFacets', newUrl);
|
||||
}
|
||||
}
|
||||
|
||||
export default RangeSlider;
|
||||
47
falcon/_dev/js/listing/index.js
Normal file
47
falcon/_dev/js/listing/index.js
Normal file
@ -0,0 +1,47 @@
|
||||
import $ from 'jquery';
|
||||
import prestashop from 'prestashop';
|
||||
import Filters from '@js/listing/components/filters/Filters';
|
||||
|
||||
function updateProductListDOM(data) {
|
||||
$(prestashop.themeSelectors.listing.searchFilters).replaceWith(
|
||||
data.rendered_facets,
|
||||
);
|
||||
$(prestashop.themeSelectors.listing.activeSearchFilters).replaceWith(
|
||||
data.rendered_active_filters,
|
||||
);
|
||||
$(prestashop.themeSelectors.listing.listTop).replaceWith(
|
||||
data.rendered_products_top,
|
||||
);
|
||||
|
||||
const renderedProducts = $(data.rendered_products);
|
||||
const productSelectors = $(prestashop.themeSelectors.listing.product);
|
||||
|
||||
if (productSelectors.length > 0) {
|
||||
productSelectors.removeClass().addClass(productSelectors.first().attr('class'));
|
||||
} else {
|
||||
productSelectors.removeClass().addClass(renderedProducts.first().attr('class'));
|
||||
}
|
||||
|
||||
$(prestashop.themeSelectors.listing.list).replaceWith(renderedProducts);
|
||||
$(prestashop.themeSelectors.listing.listBottom).replaceWith(data.rendered_products_bottom);
|
||||
|
||||
if (data.rendered_products_header) {
|
||||
$(prestashop.themeSelectors.listing.listHeader).replaceWith(data.rendered_products_header);
|
||||
}
|
||||
|
||||
prestashop.emit('updatedProductList', data);
|
||||
}
|
||||
|
||||
$(() => {
|
||||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "filters" }] */
|
||||
const filters = new Filters();
|
||||
|
||||
prestashop.on('updateProductList', (data) => {
|
||||
updateProductListDOM(data);
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
prestashop.on('updatedProductList', () => {
|
||||
prestashop.pageLazyLoad.update();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user