diff --git a/config/schema/facets.widgets.schema.yml b/config/schema/facets.widgets.schema.yml index 478a760..4e47e62 100644 --- a/config/schema/facets.widgets.schema.yml +++ b/config/schema/facets.widgets.schema.yml @@ -57,3 +57,13 @@ facet.widget.config.links: # "soft limit" and "show counts". facet.widget.config.checkbox: type: facet.widget.config.links + mapping: + disable_autosubmit: + type: label + label: 'Disable autosubmit' + submit_button_label: + type: label + label: 'Submit label' + limit_by_pages: + type: string + label: 'Limit by pages' diff --git a/js/checkbox-widget.js b/js/checkbox-widget.js index 85c5c9a..b88e60e 100644 --- a/js/checkbox-widget.js +++ b/js/checkbox-widget.js @@ -3,7 +3,7 @@ * Transforms links into checkboxes. */ -(function ($, Drupal, once) { +(function ($, Drupal, drupalSettings, once) { 'use strict'; @@ -31,21 +31,76 @@ * Turns all facet links into checkboxes. */ Drupal.facets.makeCheckboxes = function (context) { + var facetSettings; + var $widgetLinks; // Find all checkbox facet links and give them a checkbox. var $checkboxWidgets = $(once('facets-checkbox-transform', '.js-facets-checkbox-links', context)); if ($checkboxWidgets.length > 0) { $checkboxWidgets.each(function (index, widget) { var $widget = $(widget); - var $widgetLinks = $widget.find('.facet-item > a'); + $widgetLinks = $widget.find('.facet-item > a'); // Add correct CSS selector for the widget. The Facets JS API will // register handlers on that element. $widget.addClass('js-facets-widget'); + facetSettings = drupalSettings.facets[$widget.data('drupal-facet-id')]; // Transform links to checkboxes. $widgetLinks.each(Drupal.facets.makeCheckbox); + // If disable autosubmit configuration is set, add the apply button. + if (facetSettings.disable_autosubmit) { + var $applyButton = $(''); + $applyButton.on('click', function (e) { + var $widget = $(this).parent('div').siblings('.js-facets-widget'); + $widgetLinks = $widget.find('.facet-item > a'); + + Drupal.facets.disableFacet($widget); + + // Build the url based on the selected checkboxes. + var queryString = $.parseParams(window.location.href); + var currentFacetValues = []; + if (typeof queryString['f'] == 'object') { + currentFacetValues = queryString['f']; + } + + $widgetLinks.each(function () { + var $link = $(this); + var value = $widget.data('drupal-facet-alias') + ':' + $link.data('drupal-facet-item-value'); + var setted_index = $.inArray(value, currentFacetValues); + // Add the facet, if it is not already set. + if ($link.siblings('.facets-checkbox').is(':checked') && setted_index === -1) { + currentFacetValues.push(value); + // Remove the facet if set. + } else if ($link.siblings('.facets-checkbox').is(':checked') === false && $link.siblings('.facets-checkbox').is(':indeterminate') === false && setted_index !== -1) { + currentFacetValues.splice(setted_index, 1); + } + }); + + if (currentFacetValues.length > 0) { + queryString['f'] = $.extend({}, currentFacetValues); + } + else { + queryString['f'] = null; + } + var path = facetSettings.facet_source_path || window.location.href.split('?')[0]; + var queryStringObj = $.extend({}, queryString); + + // Remove pager parameter as the current page could not exist with + // the new facets. + if (typeof queryStringObj.page !== 'undefined') { + queryStringObj.page = null; + } + + var href = path + '?' + $.param(queryStringObj); + + // Trigger the facet based on the built url. + $widget.trigger('facets_filter', [ href ]); + }); + $widgetLinks.parents('.facets-widget-checkbox').append($('
').append($applyButton)); + } + // We have to trigger attaching of behaviours, so that Facets JS API can // register handlers on checkbox widgets. Drupal.attachBehaviors(this.parentNode, Drupal.settings); @@ -66,6 +121,8 @@ var description = $link.html(); var href = $link.attr('href'); var id = $link.data('drupal-facet-item-id'); + var $widget = $(this).closest('.js-facets-widget'); + var facetSettings = drupalSettings.facets[$widget.data('drupal-facet-id')]; var checkbox = $('') .attr('id', id) @@ -73,14 +130,16 @@ .data('facetsredir', href); var label = $(''); - checkbox.on('change.facets', function (e) { - e.preventDefault(); - - var $widget = $(this).closest('.js-facets-widget'); + // Do not trigger facets on change when auto-submit is disabled, so we can trigger them manually clicking on the + // apply button. + if (!facetSettings.disable_autosubmit) { + checkbox.on('change.facets', function (e) { + e.preventDefault(); - Drupal.facets.disableFacet($widget); - $widget.trigger('facets_filter', [ href ]); - }); + Drupal.facets.disableFacet($widget); + $widget.trigger('facets_filter', [href]); + }); + } if (active) { checkbox.attr('checked', true); @@ -125,4 +184,106 @@ e.preventDefault(); }; -})(jQuery, Drupal, once); +})(jQuery, Drupal, drupalSettings, once); + +// Add an URL parser to JQuery that returns an object +// This function is meant to be used with an URL like the window.location +// Use: $.parseParams('http://mysite.com/?var=string') or $.parseParams() to parse the window.location +// Simple variable: ?var=abc returns {var: "abc"} +// Simple object: ?var.length=2&var.scope=123 returns {var: {length: "2", scope: "123"}} +// Simple array: ?var[]=0&var[]=9 returns {var: ["0", "9"]} +// Array with index: ?var[0]=0&var[1]=9 returns {var: ["0", "9"]} +// Nested objects: ?my.var.is.here=5 returns {my: {var: {is: {here: "5"}}}} +// All together: ?var=a&my.var[]=b&my.cookie=no returns {var: "a", my: {var: ["b"], cookie: "no"}} +// You just cant have an object in an array, e.g. ?var[1].test=abc DOES NOT WORK +(function ($) { + var re = /([^&=]+)=?([^&]*)/g; + var decode = function (str) { + return decodeURIComponent(str.replace(/\+/g, ' ')); + }; + $.parseParams = function (query) { + + // Recursive function to construct the result object. + function createElement(params, key, value) { + var list; + key = key + ''; + + // If the key is a property. + if (key.indexOf('.') !== -1) { + // Extract the first part with the name of the object. + list = key.split('.'); + + // The rest of the key. + var new_key = key.split(/\.(.+)?/)[1]; + + // Create the object if it doesn't exist. + if (!params[list[0]]) params[list[0]] = {}; + + // If the key is not empty, create it in the object. + if (new_key !== '') { + createElement(params[list[0]], new_key, value); + } + else { + console.warn('parseParams :: empty property in key "' + key + '"'); + } + } + // If the key is an array. + else if (key.indexOf('[') !== -1) { + // extract the array name + list = key.split('['); + key = list[0]; + + // extract the index of the array + list = list[1].split(']'); + var index = list[0] + + // if index is empty, just push the value at the end of the array + if (index === '') { + if (!params) params = {}; + if (!params[key] || !$.isArray(params[key])) params[key] = []; + params[key].push(value); + } + // Add the value at the index (must be an integer). + else { + if (!params) params = {}; + if (!params[key] || !$.isArray(params[key])) params[key] = []; + params[key][parseInt(index)] = value; + } + } + // Just normal key. + else { + if (!params) params = {}; + params[key] = value; + } + } + + // Be sure the query is a string. + query = query + ''; + + if (query === '') query = window.location + ''; + + var params = {}, e; + if (query) { + // Remove # from end of query. + if (query.indexOf('#') !== -1) { + query = query.substr(0, query.indexOf('#')); + } + + // Remove ? at the beginning of the query. + if (query.indexOf('?') !== -1) { + query = query.substr(query.indexOf('?') + 1, query.length); + } else return {}; + + // Empty parameters. + if (query === '') return {}; + + // Execute a createElement on every key and value. + while (e = re.exec(query)) { + var key = decode(e[1]); + var value = decode(e[2]); + createElement(params, key, value); + } + } + return params; + }; +})(jQuery); diff --git a/src/Plugin/facets/widget/CheckboxWidget.php b/src/Plugin/facets/widget/CheckboxWidget.php index 207c1f1..0211e10 100644 --- a/src/Plugin/facets/widget/CheckboxWidget.php +++ b/src/Plugin/facets/widget/CheckboxWidget.php @@ -2,6 +2,9 @@ namespace Drupal\facets\Plugin\facets\widget; +use Drupal\Core\Form\FormStateInterface; +use Drupal\facets\FacetInterface; + /** * The checkbox / radios widget. * @@ -19,6 +22,76 @@ class CheckboxWidget extends LinksWidget { protected function appendWidgetLibrary(array &$build) { $build['#attributes']['class'][] = 'js-facets-checkbox-links'; $build['#attached']['library'][] = 'facets/drupal.facets.checkbox-widget'; + if ($this->isDisablingAllowed()) { + $build['#attached']['drupalSettings']['facets'][$this->facet->id()]['disable_autosubmit'] = $this->getConfiguration()['disable_autosubmit']; + $build['#attached']['drupalSettings']['facets'][$this->facet->id()]['submit_button_label'] = $this->getConfiguration()['submit_button_label']; + if ($facet_source = $this->facet->getFacetSource()) { + $build['#attached']['drupalSettings']['facets'][$this->facet->id()]['facet_source_path'] = $facet_source->getPath(); + } + } + } + + /** + * {@inheritdoc} + */ + public function defaultConfiguration() { + return [ + 'disable_autosubmit' => FALSE, + 'submit_button_label' => 'Apply', + 'limit_by_pages' => '', + ] + parent::defaultConfiguration(); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state, FacetInterface $facet) { + $form = parent::buildConfigurationForm($form, $form_state, $facet); + $form['disable_autosubmit'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Disable autosubmit'), + '#description' => $this->t('Disabling autosubmit will provide a button to apply the selected facet items. Otherwise the facet will be applied immediately once a checkbox is checked.'), + '#default_value' => $this->getConfiguration()['disable_autosubmit'], + ]; + $form['submit_button_label'] = [ + '#type' => 'textfield', + '#title' => $this->t('Submit label'), + '#description' => $this->t('The label for the submit button to apply the facet.'), + '#default_value' => $this->getConfiguration()['submit_button_label'], + '#states' => [ + 'visible' => [ + ':input[name="widget_config[disable_autosubmit]"]' => ['checked' => TRUE], + ], + ], + ]; + $form['limit_by_pages'] = [ + '#type' => 'textarea', + '#title' => $this->t('List of pages'), + '#description' => $this->t('List of pages where "Disable autosubmit" should work. Leave empty to use on all pages'), + '#default_value' => $this->getConfiguration()['limit_by_pages'], + '#states' => [ + 'visible' => [ + ':input[name="widget_config[disable_autosubmit]"]' => ['checked' => TRUE], + ], + ], + ]; + return $form; + } + + /** + * Check if autosubmit disabling can be applied on this page. + * + * @return bool + * TRUE - if allowed, FALSE otherwise. + */ + protected function isDisablingAllowed(): bool { + $pages = $this->getConfiguration()['limit_by_pages']; + if (empty($pages)) { + return TRUE; + } + $pages = explode(PHP_EOL, $pages); + $current_path = \Drupal::request()->getRequestUri(); + return in_array($current_path, $pages); } }