+
- =$block->getItemPrefix()?>
- =$block->renderValue($block->getMinValue())?>
- =$block->getItemPostfix()?>
+ = $itemPrefix ?>
+ =$block->renderValue($minValue)?>
+ = $itemPostfix ?>
- =$block->getItemPrefix()?>
- =$block->renderValue($block->getMaxValue())?>
- =$block->getItemPostfix()?>
+ = $itemPrefix ?>
+ =$block->renderValue($maxValue)?>
+ = $itemPostfix ?>
-
+
- =$block->getItemPrefix()?>
- =$block->renderValue($block->getCurrentMinValue())?>
- =$block->getItemPostfix()?>
+ = $itemPrefix ?>
+ = $block->renderValue($currentMinValue)?>
+ = $itemPostfix ?>
- =$block->getItemPrefix()?>
- =$block->renderValue($block->getCurrentMaxValue())?>
- =$block->getItemPostfix()?>
+ = $itemPrefix ?>
+ =$block->renderValue($currentMaxValue)?>
+ = $itemPostfix ?>
+
-
-
diff --git a/src/view/frontend/templates/product/layered/swatch.phtml b/src/view/frontend/templates/product/layered/swatch.phtml
index 8a0f8cc7..b6646a59 100644
--- a/src/view/frontend/templates/product/layered/swatch.phtml
+++ b/src/view/frontend/templates/product/layered/swatch.phtml
@@ -12,22 +12,21 @@
+ data-mage-init='{"tweakwiseNavigationSort":[]}'>
diff --git a/src/view/frontend/web/css/style.less b/src/view/frontend/web/css/style.less
index e3a21c9e..4eddd25a 100644
--- a/src/view/frontend/web/css/style.less
+++ b/src/view/frontend/web/css/style.less
@@ -3,6 +3,42 @@
.current-max-value {
float: right;
}
+
+ .ui-slider {
+ width: ~"calc(100% - 20px)";
+ margin-left: 4px;
+
+ &-handle {
+ &:hover {
+ background: #e67424;
+ }
+ }
+ }
+
+ .slider-inputs {
+ position: relative;
+ margin-top: 5px;
+ overflow: hidden;
+
+ .slider-min-wrapper,
+ .slider-max-wrapper {
+ width: 60px;
+ max-width: 48%;
+
+ label {
+ font-size: 11px;
+ font-style: italic;
+ }
+ }
+
+ .slider-min-wrapper {
+ float: left;
+ }
+
+ .slider-max-wrapper {
+ float: right;
+ }
+ }
}
.tree-attribute .child-items {
@@ -51,6 +87,8 @@
.more-items,
.less-items {
cursor: pointer;
+ color: #006bb4;
+ text-decoration: underline;
}
.filter-options-title {
@@ -61,6 +99,19 @@
content: '';
}
+ .items .item {
+ input[type="checkbox"] {
+ margin-top: 0;
+ top: 1px;
+ }
+
+ > a {
+ label {
+ cursor: pointer;
+ }
+ }
+ }
+
.swatch-attribute-options {
input[type='checkbox']:checked {
+ label .swatch-option {
@@ -75,21 +126,37 @@
position: relative;
cursor: pointer;
+ > span {
+ display: block;
+ width: 18px;
+ height: 18px;
+ background: #006bb4;
+ color: white;
+ font-family: serif;
+ line-height: 19px;
+ text-align: center;
+ border-radius: 50%;
+ text-transform: lowercase;
+ font-style: italic;
+ }
+
&:before {
content: attr(data-tooltip);
box-sizing: border-box;
display: block;
background: rgba(0, 0, 0, 0.7);
color: white;
- padding: 3px;
+ padding: 3px 10px;
position: absolute;
left: 50%;
bottom: 100%;
- margin-left: -100px;
width: 200px;
+ text-align: center;
+ transform: translate(-50%, 0);
opacity: 0;
transition: 0.25s ease-in-out;
text-transform: none;
+ font-weight: normal;
}
&:after {
@@ -111,7 +178,7 @@
&:hover {
&:before {
opacity: 1;
- bottom: ~"calc(100% + 24px)";
+ bottom: ~"calc(100% + 16px)";
}
&:after {
@@ -121,3 +188,15 @@
}
}
}
+
+.filter .filter-current-subtitle,
+.filter-title strong {
+ cursor: pointer;
+}
+
+// Fix for swatch tooltip
+.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
+ .swatch-option-tooltip {
+ display: none !important;
+ }
+}
diff --git a/src/view/frontend/web/js/lib/jquery.ui.touch-punch.min.js b/src/view/frontend/web/js/lib/jquery.ui.touch-punch.min.js
new file mode 100644
index 00000000..8c8ceb4d
--- /dev/null
+++ b/src/view/frontend/web/js/lib/jquery.ui.touch-punch.min.js
@@ -0,0 +1,11 @@
+/*!
+ * jQuery UI Touch Punch 0.2.3
+ *
+ * Copyright 2011–2014, Dave Furfero
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ *
+ * Depends:
+ * jquery.ui.widget.js
+ * jquery.ui.mouse.js
+ */
+!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
diff --git a/src/view/frontend/web/js/navigation-filter.js b/src/view/frontend/web/js/navigation-filter.js
deleted file mode 100644
index 8d1e12a2..00000000
--- a/src/view/frontend/web/js/navigation-filter.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved
- *
- * @copyright Copyright (c) 2017-2017 Tweakwise.com B.V. (https://www.tweakwise.com)
- * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
- */
-
-define(['jquery', 'jquery/ui'], function($) {
- $.widget('tweakwise.navigationFilter', {
- _hookEvents: function() {
- this.element.on('click', '.more-items', this._handleMoreItemsLink.bind(this));
- this.element.on('click', '.less-items', this._handleLessItemsLink.bind(this));
- if (!this.options.hasOwnProperty('formFilters') || !this.options.formFilters) {
- this.element.on('click', '.item input[type="checkbox"]', this._handleCheckboxClick.bind(this));
- this.element.on('click', '.js-swatch-link', this._handleSwatchClick.bind(this));
- }
- },
-
- _handleMoreItemsLink: function() {
- this._sortItems('alternate-sort');
- this.element.find('.default-hidden').show();
- this.element.find('.more-items').hide();
-
- return false;
- },
-
- _handleLessItemsLink: function() {
- this._sortItems('original-sort');
- this.element.find('.default-hidden').hide();
- this.element.find('.more-items').show();
-
- return false;
- },
-
- _sortItems: function (type) {
- if (!this.options.hasAlternateSort) {
- return;
- }
-
- let list = this.element.find('.items');
- list.children('.item').sort(function (a, b) {
- return $(a).data(type) - $(b).data(type);
- }).appendTo(list);
- },
-
- _handleCheckboxClick: function(event) {
- var a = $(event.currentTarget).closest('a');
- var href = this._findHref(a);
- if (href) {
- window.location.href = href;
- return false;
- }
- },
-
- _handleSwatchClick: function(event) {
- event.preventDefault();
- this._handleCheckboxClick(event);
- },
-
- _findHref: function (aElement) {
- var href = aElement.attr('href');
- if (this.options.hasOwnProperty('seoEnabled') && this.options.seoEnabled) {
- var seoHref = aElement.data('seo-href');
- return seoHref ? seoHref : href;
- }
-
- return href;
- },
-
- _create: function() {
- this._hookEvents();
- return this._superApply(arguments);
- }
- });
-
- return $.tweakwise.navigationFilter;
-});
\ No newline at end of file
diff --git a/src/view/frontend/web/js/navigation-form.js b/src/view/frontend/web/js/navigation-form.js
index 5a6b7411..0e6cad9c 100644
--- a/src/view/frontend/web/js/navigation-form.js
+++ b/src/view/frontend/web/js/navigation-form.js
@@ -1,112 +1,321 @@
/**
* Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved
*
- * @copyright Copyright (c) 2018-2018 Tweakwise.com B.V. (https://www.tweakwise.com)
+ * @copyright Copyright (c) 2017-2017 Tweakwise.com B.V. (https://www.tweakwise.com)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
-define(['jquery', 'jquery/ui'], function($) {
+define([
+ 'jquery',
+], function ($) {
$.widget('tweakwise.navigationForm', {
- _hookEvents: function() {
- if (this.options.hasOwnProperty('formFilters') && this.options.formFilters === "1") {
- this.element.on('submit', this._handleFilterButtonClick.bind(this));
- }
+
+ options: {
+ ajaxFilters: false,
+ formFilters: false,
+ seoEnabled: false,
+ ajaxEndpoint: '/tweakwise/ajax/navigation',
+ filterSelector: '#layered-filter-block',
+ productListSelector: '.products.wrapper',
+ toolbarSelector: '.toolbar.toolbar-products',
+ isLoading: false,
},
- _handleFilterButtonClick: function(event) {
- event.preventDefault();
+ currentXhr: null,
- let values = jQuery(this.element).serialize();
- let sliderValues = this._getSliderUrlParameters();
- let searchValue = this._getSearchParam();
- let catValue = this._getCatParam();
+ _create: function () {
+ this._hookEvents();
+ return this._superApply(arguments);
+ },
- let url = '?';
- if (searchValue) {
- url = url + searchValue;
+ /**
+ * Bind filter events, these are filter click and filter remove
+ *
+ * @private
+ */
+ _hookEvents: function () {
+ if (this.options.ajaxFilters) {
+ this._bindPopChangeHandler()
}
+ this._bindFilterClickEvents();
+ this._bindFilterRemoveEvents();
+ },
- if (values && url !== '?') {
- url = url + '&' + values;
- } else if (values) {
- url = url + values;
+ /**
+ * Bind filter click events
+ *
+ * @private
+ */
+ _bindFilterClickEvents: function () {
+ if (this.options.formFilters) {
+ this.element.on('click', '.js-btn-filter', this._getFilterClickHandler().bind(this));
+ } else {
+ this.element.on('change', this._getFilterClickHandler().bind(this));
}
+ },
+
+ /**
+ * Filter remove events are only relevant for ajax filtering. If ajaxFilters is false then we just navigate
+ * to the url specified in the a.
+ *
+ * @private
+ */
+ _bindFilterRemoveEvents: function () {
+ if (this.options.ajaxFilters) {
+ this.element.on('click', 'a.remove', this._ajaxClearHandler.bind(this));
+ }
+ },
- if (catValue && url !== '?') {
- url = url + '&' + catValue;
- } else if (catValue) {
- url = url + catValue;
+ /**
+ *
+ * @private
+ */
+ _bindPopChangeHandler: function () {
+ window.onpopstate = function (event) {
+ if (event.state && event.state.html) {
+ this._updateBlocks(event.state.html);
+ }
+ }.bind(this);
+ },
+
+ /**
+ * Should return the handler for the filter event, depends on config options.
+ * Supported options are ajax filtering and form filters and any combination of those options.
+ * Note that the ajaxHandler also handles the case ajax enabled AND form filters enabled
+ *
+ * @returns {tweakwise.navigationFilterAjax._ajaxHandler|tweakwise.navigationFilterAjax._defaultHandler}
+ * @private
+ */
+ _getFilterClickHandler: function () {
+ if (this.options.ajaxFilters) {
+ return this._ajaxHandler;
}
- if (sliderValues && url !== '?') {
- url = url + '&' + sliderValues;
- } else if (sliderValues) {
- url = url + sliderValues;
+ if (this.options.formFilters) {
+ return this._formFilterHandler;
}
- if (url !== '?') {
- window.location = url;
+ return this._defaultHandler
+ },
+
+ /**
+ * Serialize the form element but skip unwanted inputs
+ *
+ * @returns {*}
+ * @private
+ */
+ _getFilterParameters: function () {
+ return this.element.find(':not(.js-skip-submit)').serialize();
+ },
+
+ // ------- Default filter handling (i.e. no ajax and no filter form)
+ /**
+ * Navigate to the selected filter url
+ *
+ * @param event
+ * @returns {boolean}
+ * @private
+ */
+ _defaultHandler: function (event) {
+ var a = $(event.target).closest('a');
+ var href = this._findHref(a);
+ if (href) {
+ window.location.href = href;
+ return false;
}
},
- _getSearchParam: function() {
- let q = this._getQParam();
- let searchParam = {};
- if (q) {
- searchParam['q'] = q;
- return jQuery.param(searchParam);
+ /**
+ * Should return the url to navigate to
+ *
+ * @param aElement
+ * @returns {*}
+ * @private
+ */
+ _findHref: function (aElement) {
+ var href = aElement.attr('href');
+ if (this.options.seoEnabled) {
+ var seoHref = aElement.data('seo-href');
+ return seoHref ? seoHref : href;
}
- return '';
+ return href;
},
- _getQParam: function() {
- let matches = window.location.search.match(/(\?|&)q\=([^&]*)/);
- if (matches && matches[2]) {
- let trimmedMatch = matches[2].replace(/\+/g, ' '),
- searchVal = jQuery('#search').val();
+ /**
+ * This provides a means to disable ajax filtering.
+ * If you dont want ajax filtering for certain filters add a data-no-ajax attribute.
+ *
+ * @param event
+ * @returns {boolean}
+ * @private
+ */
+ _allowAjax: function (event) {
+ var a = $(event.target).closest('a');
+ return !a.data('no-ajax');
+ },
- if (searchVal === trimmedMatch) {
- return decodeURIComponent(trimmedMatch);
- }
+ // ------- End of default filter handling
+
+ // ------- Handling for ajax filtering (i.e. only ajax filtering)
+ /**
+ * Handle Ajax request for new content
+ *
+ * @param event
+ * @private
+ */
+ _ajaxHandler: function (event) {
+ event.preventDefault();
- return decodeURIComponent(matches[2]);
+ if (this.currentXhr) {
+ this.currentXhr.abort();
}
- return '';
+ if (!this._allowAjax(event)) {
+ this._defaultHandler(event);
+ return;
+ }
+
+ this._startLoader();
+ this.currentXhr = $.ajax({
+ url: this.options.ajaxEndpoint,
+ data: this._getFilterParameters(),
+ cache: true,
+ success: function (response) {
+ this._updateBlocks(response.html);
+ this._updateState(response);
+ }.bind(this),
+ error: function (jqXHR, errorStatus) {
+ if (errorStatus !== 'abort') {
+ // Something went wrong, try to navigate to the selected filter
+ this._defaultHandler(event);
+ }
+ }.bind(this),
+ complete: function () {
+ this._stopLoader();
+ }.bind(this)
+ });
},
- _getCatParam: function () {
- let matches = window.location.search.match(/(\?|&)categorie\=([^&]*)/);
- if (matches && matches[2]) {
- let catParam = {categorie: matches[2]};
- return jQuery.param(catParam);
+ /**
+ * Handle filter clear events
+ *
+ * @param event
+ * @private
+ */
+ _ajaxClearHandler: function (event) {
+ var filterId = '#' + $(event.target).data('js-filter-id');
+ var filter = this.element.find(filterId);
+ if (filter && filter.length) {
+ event.preventDefault();
+ filter = $(filter);
+ // Set filter disabled so that it will not be submitted when change is triggered
+ filter.attr('disabled', true);
+ if (this.options.formFilters) {
+ // Simulate click so that the form will be submitted
+ this.element.find('.js-btn-filter').first().trigger('click');
+ } else {
+ filter.trigger('change');
+ }
+ }
+ },
+
+ /**
+ * Update all relevant html with response data, trigger contentUpdated to 'trigger' data-mage-init
+ * @param htmlResponse
+ * @private
+ */
+ _updateBlocks: function (htmlResponse) {
+ var filterSelector = this.options.filterSelector;
+ var productListSelector = this.options.productListSelector;
+ var toolbarSelector = this.options.toolbarSelector;
+
+ var wrapper = document.createElement('div');
+ wrapper.innerHTML = htmlResponse;
+ var parsedHtml = $(wrapper);
+
+ var newFiltersHtml = parsedHtml.find(filterSelector).html();
+ var newProductListHtml = parsedHtml.find(productListSelector).html();
+ var newToolbar = parsedHtml.find(toolbarSelector);
+ // Toolbar is included twice in the response
+ // We use this first().get(0) construction to access outerHTML
+ // The reason for this is that we need to replace the entire toolbar because otherwise
+ // the data-mage-init attribute is no longer available on the toolbar and hence the toolbar
+ // would not be initialized when $('body').trigger('contentUpdated'); is called
+ var newToolbarFirst = newToolbar.first().get(0);
+ var newToolbarLast = newToolbar.last().get(0);
+
+ if (newFiltersHtml) {
+ $(filterSelector).html(newFiltersHtml);
+ }
+
+ var toolbar = $(toolbarSelector);
+ /*
+ The product list comes after the toolbar.
+ We use this construction as there could be more product lists on the page
+ and we dont want to replace them all
+ */
+ if (newProductListHtml) {
+ toolbar
+ .siblings(productListSelector)
+ .html(newProductListHtml);
+ }
+ if (newToolbarFirst) {
+ toolbar
+ .first()
+ .replaceWith(newToolbarFirst.outerHTML);
+ }
+ if (newToolbarLast) {
+ toolbar
+ .last()
+ .replaceWith(newToolbarLast.outerHTML);
}
- return '';
+ $('body').trigger('contentUpdated');
},
- _getSliderUrlParameters: function() {
- let query = {};
- jQuery('.slider-attribute').each(function(i, slider) {
- slider = jQuery(slider);
- let key = slider.data('url-key');
- let min = slider.data('min');
- let max = slider.data('max');
- let rangeMin = slider.data('range-min');
- let rangeMax = slider.data('range-max');
- if ((min && max) && (rangeMin !== min || rangeMax !== max)) {
- query[key] = min + '-' + max;
- }
- });
+ /**
+ *
+ * @param response
+ * @private
+ */
+ _updateState: function (response) {
+ window.history.pushState({html: response.html}, '', response.url);
+ },
- return jQuery.param(query);
+ /**
+ * Start loader targeting body relevant for ajax filtering
+ * @private
+ */
+ _startLoader: function () {
+ $(this.options.productListSelector).trigger('processStart');
+ this.options.isLoading = true;
},
- _create: function() {
- this._hookEvents();
- return this._superApply(arguments);
+ /**
+ * Stop Loader targeting body relevant for ajax filtering
+ * @private
+ */
+ _stopLoader: function () {
+ $(this.options.productListSelector).trigger('processStop');
+ this.options.isLoading = false;
+ },
+ // ------- End of handling for ajax filtering
+
+ // ------- Handling for form filters.
+ // ------- Note that is only used when ajax is not enabled and form filters is enabled
+ /**
+ * This just handles the filter button click
+ *
+ * @private
+ */
+ _formFilterHandler: function () {
+ var filterUrl = this._getFilterParameters();
+ if (filterUrl) {
+ window.location = '?' + filterUrl;
+ }
}
+ // ------- End of handling for form filters
});
return $.tweakwise.navigationForm;
diff --git a/src/view/frontend/web/js/navigation-slider-compat.js b/src/view/frontend/web/js/navigation-slider-compat.js
new file mode 100644
index 00000000..3347a869
--- /dev/null
+++ b/src/view/frontend/web/js/navigation-slider-compat.js
@@ -0,0 +1,196 @@
+/**
+ * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved
+ *
+ * @copyright Copyright (c) (https://www.tweakwise.com)
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
+ */
+
+define([
+ 'jquery',
+ 'jquery/ui',
+ 'jQueryTouchPunch',
+ 'domReady!'
+], function($) {
+ $.widget('tweakwise.navigationSlider', {
+
+ options: {
+ filterUrl: '',
+ prefix: '',
+ postfix: '',
+ container: '',
+ min: 0,
+ max: 99999999,
+ currentMin: 0,
+ currentMax: 99999999,
+ formFilters: false,
+ ajaxFilters: false,
+ },
+
+ /**
+ *
+ * @returns {*}
+ * @private
+ */
+ _create: function() {
+ this._createSlider();
+ this._bindInputChangeEvents();
+ return this._superApply(arguments);
+ },
+
+ /**
+ * Register the correct handler depending on configuration
+ * @private
+ */
+ _createSlider: function() {
+ $(this.options.container).find('.slider').slider(this._getSliderConfig());
+ },
+
+ /**
+ *
+ * @returns {{min: number, max: number, slide: *, values: [tweakwise.navigationSlider._getSliderConfig.options.currentMin, tweakwise.navigationSlider._getSliderConfig.options.currentMax], change: *, range: boolean}}
+ * @private
+ */
+ _getSliderConfig: function() {
+ return {
+ range: true,
+ min: this.options.min,
+ max: this.options.max,
+ values: [
+ this.options.currentMin, this.options.currentMax
+ ],
+
+ slide: function (event, ui) {
+ var container = $(this.options.container);
+ var minValue = ui.values[0];
+ var maxValue = ui.values[1];
+ container.find('.current-min-value').html(this._labelFormat(minValue));
+ container.find('.current-max-value').html(this._labelFormat(maxValue));
+ container.find('input.slider-min').val(minValue);
+ container.find('input.slider-max').val(maxValue);
+
+ var sliderUrlValue = minValue + '-' + maxValue;
+ var sliderUrlInput = container.find('input.slider-url-value');
+ sliderUrlInput.val(sliderUrlValue);
+
+ this._updateSliderDisabledAttribute(sliderUrlInput, sliderUrlValue);
+ }.bind(this),
+
+ change: this._getChangeHandler()
+ }
+ },
+
+ /**
+ * Bind handling for manual input
+ *
+ * @private
+ */
+ _bindInputChangeEvents: function() {
+ var sliderContainer = $(this.options.container);
+ sliderContainer.on('change', '.slider-min', this._updateSliderUrlInput.bind(this));
+ sliderContainer.on('change', '.slider-max', this._updateSliderUrlInput.bind(this));
+ },
+
+ /**
+ * Fire slider change event
+ *
+ * @private
+ */
+ _updateSliderUrlInput: function () {
+ var sliderContainer = $(this.options.container);
+ var sliderUrlInput = sliderContainer.find('.slider-url-value');
+ var minValue = sliderContainer.find('.slider-min').val();
+ var maxValue = sliderContainer.find('.slider-max').val();
+ var inputValue = minValue + '-' + maxValue;
+ sliderUrlInput.val(inputValue);
+ this._updateSliderDisabledAttribute(sliderUrlInput, inputValue)
+ },
+
+ /**
+ *
+ * @param sliderUrlInput
+ * @param inputValue
+ * @private
+ */
+ _updateSliderDisabledAttribute: function (sliderUrlInput, inputValue) {
+ if (inputValue === sliderUrlInput.data('disabled-input')) {
+ sliderUrlInput.attr('disabled', true);
+ } else {
+ sliderUrlInput.removeAttr('disabled');
+ }
+ },
+
+ /**
+ * This determines the "slide" handler depending on configuration
+ *
+ * @returns {*}
+ * @private
+ */
+ _getChangeHandler: function () {
+ if (this.options.formFilters) {
+ return this.formFilterChange.bind(this);
+ }
+
+ if (this.options.ajaxFilters) {
+ return this.ajaxChange.bind(this);
+ }
+
+ return this.defaultChange.bind(this);
+ },
+
+ /**
+ * Standard navigation, i.e. no ajax or formfilter options
+ *
+ * @param event
+ * @param ui
+ */
+ defaultChange: function (event, ui) {
+ var min = ui.values[0];
+ var max = ui.values[1];
+ var url = this.options.filterUrl;
+
+ url = url.replace(encodeURI('{{from}}'), min);
+ url = url.replace('{{from}}', min);
+ url = url.replace(encodeURI('{{to}}'), max);
+ url = url.replace('{{to}}', max);
+ window.location.href = url;
+ },
+
+ /**
+ * Ajax navigation, no formfilters
+ *
+ * @param event
+ * @param ui
+ */
+ ajaxChange: function (event, ui) {
+ this.formFilterChange(event, ui);
+ $(this.element).closest('form').trigger('change');
+ },
+
+ /**
+ * Used when form filters is set to true, just update the values, navigation is handled by the filter button
+ *
+ * @param event
+ * @param ui
+ */
+ formFilterChange: function (event, ui) {
+ var min = ui.values[0];
+ var max = ui.values[1];
+ var sliderContainer = $(this.options.container);
+ sliderContainer.data('min', min);
+ sliderContainer.data('max', max);
+ },
+
+ /**
+ * Format slider label
+ *
+ * @param value
+ * @returns {string}
+ * @private
+ */
+ _labelFormat: function (value) {
+ return this.options.prefix + value + this.options.postfix;
+ }
+ });
+
+ return $.tweakwise.navigationSlider;
+});
diff --git a/src/view/frontend/web/js/navigation-slider.js b/src/view/frontend/web/js/navigation-slider.js
new file mode 100644
index 00000000..c15fcab8
--- /dev/null
+++ b/src/view/frontend/web/js/navigation-slider.js
@@ -0,0 +1,196 @@
+/**
+ * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved
+ *
+ * @copyright Copyright (c) (https://www.tweakwise.com)
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
+ */
+
+define([
+ 'jquery',
+ 'jquery-ui-modules/slider',
+ 'jQueryTouchPunch',
+ 'domReady!'
+], function ($) {
+ $.widget('tweakwise.navigationSlider', {
+
+ options: {
+ filterUrl: '',
+ prefix: '',
+ postfix: '',
+ container: '',
+ min: 0,
+ max: 99999999,
+ currentMin: 0,
+ currentMax: 99999999,
+ formFilters: false,
+ ajaxFilters: false,
+ },
+
+ /**
+ *
+ * @returns {*}
+ * @private
+ */
+ _create: function () {
+ this._createSlider();
+ this._bindInputChangeEvents();
+ return this._superApply(arguments);
+ },
+
+ /**
+ * Register the correct handler depending on configuration
+ * @private
+ */
+ _createSlider: function () {
+ $(this.options.container).find('.slider').slider(this._getSliderConfig());
+ },
+
+ /**
+ *
+ * @returns {{min: number, max: number, slide: *, values: [tweakwise.navigationSlider._getSliderConfig.options.currentMin, tweakwise.navigationSlider._getSliderConfig.options.currentMax], change: *, range: boolean}}
+ * @private
+ */
+ _getSliderConfig: function () {
+ return {
+ range: true,
+ min: this.options.min,
+ max: this.options.max,
+ values: [
+ this.options.currentMin, this.options.currentMax
+ ],
+
+ slide: function (event, ui) {
+ var container = $(this.options.container);
+ var minValue = ui.values[0];
+ var maxValue = ui.values[1];
+ container.find('.current-min-value').html(this._labelFormat(minValue));
+ container.find('.current-max-value').html(this._labelFormat(maxValue));
+ container.find('input.slider-min').val(minValue);
+ container.find('input.slider-max').val(maxValue);
+
+ var sliderUrlValue = minValue + '-' + maxValue;
+ var sliderUrlInput = container.find('input.slider-url-value');
+ sliderUrlInput.val(sliderUrlValue);
+
+ this._updateSliderDisabledAttribute(sliderUrlInput, sliderUrlValue);
+ }.bind(this),
+
+ change: this._getChangeHandler()
+ }
+ },
+
+ /**
+ * Bind handling for manual input
+ *
+ * @private
+ */
+ _bindInputChangeEvents: function () {
+ var sliderContainer = $(this.options.container);
+ sliderContainer.on('change', '.slider-min', this._updateSliderUrlInput.bind(this));
+ sliderContainer.on('change', '.slider-max', this._updateSliderUrlInput.bind(this));
+ },
+
+ /**
+ * Fire slider change event
+ *
+ * @private
+ */
+ _updateSliderUrlInput: function () {
+ var sliderContainer = $(this.options.container);
+ var sliderUrlInput = sliderContainer.find('.slider-url-value');
+ var minValue = sliderContainer.find('.slider-min').val();
+ var maxValue = sliderContainer.find('.slider-max').val();
+ var inputValue = minValue + '-' + maxValue;
+ sliderUrlInput.val(inputValue);
+ this._updateSliderDisabledAttribute(sliderUrlInput, inputValue)
+ },
+
+ /**
+ *
+ * @param sliderUrlInput
+ * @param inputValue
+ * @private
+ */
+ _updateSliderDisabledAttribute: function (sliderUrlInput, inputValue) {
+ if (inputValue === sliderUrlInput.data('disabled-input')) {
+ sliderUrlInput.attr('disabled', true);
+ } else {
+ sliderUrlInput.removeAttr('disabled');
+ }
+ },
+
+ /**
+ * This determines the "slide" handler depending on configuration
+ *
+ * @returns {*}
+ * @private
+ */
+ _getChangeHandler: function () {
+ if (this.options.formFilters) {
+ return this.formFilterChange.bind(this);
+ }
+
+ if (this.options.ajaxFilters) {
+ return this.ajaxChange.bind(this);
+ }
+
+ return this.defaultChange.bind(this);
+ },
+
+ /**
+ * Standard navigation, i.e. no ajax or formfilter options
+ *
+ * @param event
+ * @param ui
+ */
+ defaultChange: function (event, ui) {
+ var min = ui.values[0];
+ var max = ui.values[1];
+ var url = this.options.filterUrl;
+
+ url = url.replace(encodeURI('{{from}}'), min);
+ url = url.replace('{{from}}', min);
+ url = url.replace(encodeURI('{{to}}'), max);
+ url = url.replace('{{to}}', max);
+ window.location.href = url;
+ },
+
+ /**
+ * Ajax navigation, no formfilters
+ *
+ * @param event
+ * @param ui
+ */
+ ajaxChange: function (event, ui) {
+ this.formFilterChange(event, ui);
+ $(this.element).closest('form').trigger('change');
+ },
+
+ /**
+ * Used when form filters is set to true, just update the values, navigation is handled by the filter button
+ *
+ * @param event
+ * @param ui
+ */
+ formFilterChange: function (event, ui) {
+ var min = ui.values[0];
+ var max = ui.values[1];
+ var sliderContainer = $(this.options.container);
+ sliderContainer.data('min', min);
+ sliderContainer.data('max', max);
+ },
+
+ /**
+ * Format slider label
+ *
+ * @param value
+ * @returns {string}
+ * @private
+ */
+ _labelFormat: function (value) {
+ return this.options.prefix + value + this.options.postfix;
+ }
+ });
+
+ return $.tweakwise.navigationSlider;
+});
diff --git a/src/view/frontend/web/js/navigation-sort.js b/src/view/frontend/web/js/navigation-sort.js
new file mode 100644
index 00000000..20df69b5
--- /dev/null
+++ b/src/view/frontend/web/js/navigation-sort.js
@@ -0,0 +1,79 @@
+/**
+ * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved
+ *
+ * @copyright Copyright (c) 2017-2017 Tweakwise.com B.V. (https://www.tweakwise.com)
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
+ */
+
+define([
+ 'jquery'
+], function ($) {
+ $.widget('tweakwise.navigationSort', {
+
+ options: {
+ hasAlternateSort: null,
+ },
+
+ _create: function () {
+ this._hookEvents();
+ return this._superApply(arguments);
+ },
+
+ /**
+ * Bind more and less items click handlers
+ *
+ * @private
+ */
+ _hookEvents: function () {
+ this.element.on('click', '.more-items', this._handleMoreItemsLink.bind(this));
+ this.element.on('click', '.less-items', this._handleLessItemsLink.bind(this));
+ },
+
+ /**
+ * Sort items depending on alternate sort (this comes from tweakwise api) and expand filter list
+ *
+ * @returns {boolean}
+ * @private
+ */
+ _handleMoreItemsLink: function () {
+ this._sortItems('alternate-sort');
+ this.element.find('.default-hidden').show();
+ this.element.find('.more-items').hide();
+
+ return false;
+ },
+
+ /**
+ * Sort items depending on alternate sort (this comes from tweakwise api) and abbreviate filter list
+ *
+ * @returns {boolean}
+ * @private
+ */
+ _handleLessItemsLink: function () {
+ this._sortItems('original-sort');
+ this.element.find('.default-hidden').hide();
+ this.element.find('.more-items').show();
+
+ return false;
+ },
+
+ /**
+ * Sort items based on alternate sort (if available)
+ *
+ * @param type
+ * @private
+ */
+ _sortItems: function (type) {
+ if (!this.options.hasAlternateSort) {
+ return;
+ }
+
+ var list = this.element.find('.items');
+ list.children('.item').sort(function (a, b) {
+ return $(a).data(type) - $(b).data(type);
+ }).appendTo(list);
+ },
+ });
+
+ return $.tweakwise.navigationSort;
+});
diff --git a/src/view/frontend/web/js/quick-search.js b/src/view/frontend/web/js/quick-search.js
index 60b5ffff..f28a9c3f 100644
--- a/src/view/frontend/web/js/quick-search.js
+++ b/src/view/frontend/web/js/quick-search.js
@@ -8,10 +8,10 @@
define([
'jquery',
'Magento_Search/form-mini'
-], function($, quickSearch){
+], function ($, quickSearch) {
$.widget('tweakwise.quickSearch', quickSearch, {
- getSelectedProductUrl: function() {
+ getSelectedProductUrl: function () {
if (!this.responseList.selected) {
return null;
}
@@ -19,29 +19,26 @@ define([
return this.responseList.selected.data('url');
},
- _create: function() {
- $(this.options.formSelector).on('submit', function(event) {
+ _create: function () {
+ $(this.options.formSelector).on('submit', function (event) {
if (this.getSelectedProductUrl()) {
event.preventDefault();
}
}.bind(this));
var templateId = '#autocomplete-item-template';
- this.options.template = templateId;
this.options.url = $(templateId).data('url');
return this._superApply(arguments);
},
- _onSubmit: function (e) {
+ _onSubmit: function () {
var url = this.getSelectedProductUrl();
if (!url) {
return this._superApply(arguments);
}
- if (url !== null) {
- window.location.href = url;
- }
+ window.location.href = url;
},
_onPropertyChange: function () {
@@ -49,11 +46,11 @@ define([
clearTimeout(this.searchDelayTimeout);
}
- this.searchDelayTimeout = setTimeout(function() {
+ this.searchDelayTimeout = setTimeout(function () {
quickSearch.prototype._onPropertyChange.apply(this);
}.bind(this), 200);
}
});
return $.tweakwise.quickSearch;
-});
\ No newline at end of file
+});
diff --git a/src/view/frontend/web/js/toolbar.js b/src/view/frontend/web/js/toolbar.js
new file mode 100644
index 00000000..e936fd98
--- /dev/null
+++ b/src/view/frontend/web/js/toolbar.js
@@ -0,0 +1,74 @@
+/**
+ * Tweakwise & Emico (https://www.tweakwise.com/ & https://www.emico.nl/) - All Rights Reserved
+ *
+ * @copyright Copyright (c) 2017-2017 Tweakwise.com B.V. (https://www.tweakwise.com)
+ * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
+ */
+
+define([
+ 'jquery',
+ 'Magento_Catalog/js/product/list/toolbar'
+], function ($, productListToolbarForm) {
+ $.widget('tweakwise.productListToolbarForm', productListToolbarForm, {
+
+ options: {
+ ajaxFilters: false,
+ pagerItemSelector: '.pages li.item',
+ filterFormSelector: '#facet-filter-form'
+ },
+
+ /** @inheritdoc */
+ _create: function () {
+ var options = this.options;
+ var element = this.element;
+ this._bind(element.find(options.modeControl), options.mode, options.modeDefault);
+ this._bind(element.find(options.directionControl), options.direction, options.directionDefault);
+ this._bind(element.find(options.orderControl), options.order, options.orderDefault);
+ this._bind(element.find(options.limitControl), options.limit, options.limitDefault);
+ if (options.ajaxFilters) {
+ $(element).on('click', options.pagerItemSelector, this.handlePagerClick.bind(this));
+ }
+ },
+
+ handlePagerClick: function (event) {
+ event.preventDefault();
+ var anchor = $(event.target).closest('a');
+ var page = anchor.attr('href') || '';
+ var pageValueRegex = '[?&]p=(\\\d?)';
+ var pageValue = new RegExp(pageValueRegex).exec(page);
+ if (pageValue) {
+ pageValue = pageValue[1];
+ this.changeUrl('p', pageValue, pageValue)
+ }
+
+ return false;
+ },
+
+ /**
+ * @param {String} paramName
+ * @param {*} paramValue
+ * @param {*} defaultValue
+ */
+ changeUrl: function (paramName, paramValue, defaultValue) {
+ if (!this.options.ajaxFilters) {
+ return this._super(paramName, paramValue, defaultValue);
+ }
+
+ var form = $(this.options.filterFormSelector);
+ var input = form.find('input[name=' + paramName + ']');
+ if (!input.length) {
+ input = document.createElement('input');
+ input.name = paramName;
+ input = $(input);
+ form.append(input);
+ input.hide();
+ }
+
+ input.attr('value', paramValue);
+ form.trigger('change');
+ }
+
+ });
+
+ return $.tweakwise.productListToolbarForm;
+});