diff --git a/ajax_facets.info b/ajax_facets.info index 81b9436..e9d2b82 100644 --- a/ajax_facets.info +++ b/ajax_facets.info @@ -3,6 +3,7 @@ description = Ajax facet filters. dependencies[] = facetapi dependencies[] = search_api dependencies[] = views +dependencies[] = libraries package = Search Toolkit core = 7.x diff --git a/ajax_facets.install b/ajax_facets.install index e69de29..277d589 100644 --- a/ajax_facets.install +++ b/ajax_facets.install @@ -0,0 +1,33 @@ + $t('Ajax Facets'), + 'description' => $t('Please download and unpack "history.js" library to libraries directory.'), + 'value' => l($t('Download'), 'https://github.com/browserstate/history.js/', array( + 'attributes' => array( + 'target' => '_blank', + ), + )), + 'severity' => REQUIREMENT_ERROR, + ); + } + break; + } + + return $requirements; +} diff --git a/ajax_facets.module b/ajax_facets.module index e160e9a..8d2bfd6 100644 --- a/ajax_facets.module +++ b/ajax_facets.module @@ -95,6 +95,7 @@ function ajax_facets_add_ajax_js($facet) { $included = TRUE; $module_path = drupal_get_path('module', 'ajax_facets'); drupal_add_js($module_path . '/misc/ajax_facets.js'); + drupal_add_js(libraries_get_path('history.js') . '/scripts/bundled/html4+html5/jquery.history.js', array('group' => JS_LIBRARY)); drupal_add_css($module_path . '/misc/ajax_facets.css'); $search_path = $facet->getAdapter()->getSearchPath(); $filter_key = $facet->getAdapter()->getUrlProcessor()->getFilterKey(); diff --git a/misc/ajax_facets.js b/misc/ajax_facets.js index 731d72c..7dc5b3a 100644 --- a/misc/ajax_facets.js +++ b/misc/ajax_facets.js @@ -8,6 +8,8 @@ Drupal.ajax_facets.queryState = null; // State of each facet. Drupal.ajax_facets.facetQueryState = null; + // Determine if it is first page load. Will be reset in Drupal.ajax_facets.initHistoryState. + Drupal.ajax_facets.firstLoad = true; // HTML ID of element of current facet. Drupal.ajax_facets.current_id = null; // Current changed facet. @@ -185,7 +187,7 @@ var $facet = $(this).parent().find('[data-facet]').first(); var facetName = $facet.data('facet'); Drupal.ajax_facets.excludeCurrentFacet(facetName); - Drupal.ajax_facets.sendAjaxQuery($facet); + Drupal.ajax_facets.sendAjaxQuery($facet, true); event.preventDefault(); }); }; @@ -197,6 +199,9 @@ var $this = $(this); var facetName = $this.data('facet'); + + Drupal.ajax_facets.initHistoryState($this); + if (Drupal.ajax_facets.queryState['f'] != undefined) { // Exclude all values for this facet from query. Drupal.ajax_facets.excludeCurrentFacet(facetName); @@ -210,7 +215,7 @@ } } - Drupal.ajax_facets.sendAjaxQuery($this); + Drupal.ajax_facets.sendAjaxQuery($this, true); }; /** @@ -219,6 +224,9 @@ Drupal.ajax_facets.processCheckboxes = function (event) { var $this = $(this); var facetName = $this.data('facet'); + + Drupal.ajax_facets.initHistoryState($this); + var facetCheckboxName = $this.attr('name'); if (Drupal.ajax_facets.queryState['f'] != undefined) { var queryNew = new Array(); @@ -250,7 +258,7 @@ } } - Drupal.ajax_facets.sendAjaxQuery($this); + Drupal.ajax_facets.sendAjaxQuery($this, true); }; /** @@ -260,6 +268,9 @@ var $this = $(this); var facetName = $this.data('facet'); var name_value = $this.data('name') + ':' + $this.data('value'); + + Drupal.ajax_facets.initHistoryState($this); + if (Drupal.ajax_facets.queryState['f'] != undefined) { var queryNew = new Array(); /* Handle value - deactivate. */ @@ -289,7 +300,7 @@ } } - Drupal.ajax_facets.sendAjaxQuery($this); + Drupal.ajax_facets.sendAjaxQuery($this, true); event.preventDefault(); }; @@ -297,6 +308,7 @@ * Callback for slide event for widget ranges. */ Drupal.ajax_facets.processSlider = function($sliderWrapper, min, max) { + Drupal.ajax_facets.initHistoryState($sliderWrapper); window.clearTimeout(Drupal.ajax_facets.timer); Drupal.ajax_facets.timer = window.setTimeout(function() { var facetName = $sliderWrapper.data('facet'); @@ -306,7 +318,7 @@ Drupal.ajax_facets.queryState['f'][Drupal.ajax_facets.queryState['f'].length] = facetName + ':[' + min + ' TO ' + max + ']'; } - Drupal.ajax_facets.sendAjaxQuery($sliderWrapper); + Drupal.ajax_facets.sendAjaxQuery($sliderWrapper, true); }, 600); } @@ -327,7 +339,7 @@ /** * Send ajax. */ - Drupal.ajax_facets.sendAjaxQuery = function ($this) { + Drupal.ajax_facets.sendAjaxQuery = function ($this, pushStateNeeded) { Drupal.ajax_facets.current_id = $this.attr('id'); Drupal.ajax_facets.current_facet_name = $this.data('facet'); Drupal.ajax_facets.beforeAjax(); @@ -343,6 +355,22 @@ submit : {'ajax_facets' : data} }; var ajax = new Drupal.ajax(false, false, settings); + ajax.success = function(response, status) { + // Push new state only on successful ajax response. + if (pushStateNeeded) { + var stateUrl = Drupal.settings.basePath + Drupal.settings.pathPrefix + Drupal.settings.facetapi.searchPath + '?' + Drupal.ajax_facets.getFacetsQuery(), + state = { + current_id: Drupal.ajax_facets.current_id, + current_facet_name: Drupal.ajax_facets.current_facet_name, + facets: Drupal.ajax_facets.queryState['f'] + }; + + Drupal.ajax_facets.pushState(state, document.title, stateUrl); + } + + //Pass back to original method. + Drupal.ajax.prototype.success.call(this, response, status); + }; ajax.eventResponse(ajax, {}); }, @@ -464,4 +492,79 @@ } } } + + /** + * Returns query string with selected facets. + */ + Drupal.ajax_facets.getFacetsQuery = function() { + var query = { + 'f': Drupal.ajax_facets.queryState.f + }; + + if (Drupal.ajax_facets.queryState.query) { + query.query = Drupal.ajax_facets.queryState.query; + } + + if (Drupal.ajax_facets.queryState.order) { + query.order = Drupal.ajax_facets.queryState.order; + query.sort = Drupal.ajax_facets.queryState.sort; + } + + if (Drupal.ajax_facets.queryState.pages) { + query.pages = Drupal.ajax_facets.queryState.pages; + } + + return decodeURIComponent($.param(query)); + }; + + /** + * Initialize the history state. We only want to do this on the initial page + * load but we can't call it until after a facet has been clicked because we + * need to communicate which one is being "deactivated" for our ajax success + * handler. + */ + Drupal.ajax_facets.initHistoryState = function ($facet) { + // Set the initial state only initial page load. + if (Drupal.ajax_facets.firstLoad) { + History.replaceState({ + current_id: $facet.attr('id'), + current_facet_name: $facet.data('facet'), + facets: Drupal.ajax_facets.queryState['f'] + }, null, null); + Drupal.ajax_facets.firstLoad = false; + } + }; + + /** + * Pushes new state to browser history. + * + * History.js library fires "statechange" event even on API push/replace calls. + * So before pushing new state to history we should unbind from this event and after bin again. + */ + Drupal.ajax_facets.pushState = function (state, title, stateUrl) { + var $window = $(window); + + $window.unbind('statechange', Drupal.ajax_facets.reactOnStateChange); + History.pushState(state, title, stateUrl); + $window.bind('statechange', Drupal.ajax_facets.reactOnStateChange); + }; + + /** + * Callback for back/forward browser buttons. + */ + Drupal.ajax_facets.reactOnStateChange = function () { + var state = History.getState(); + + Drupal.ajax_facets.queryState['f'] = state.data.facets; + Drupal.ajax_facets.sendAjaxQuery($('#' + state.data.current_id), false); + }; + + /** + * Initial bind to statechange event. + */ + History.Adapter.bind(window, 'statechange', function () { + if (Drupal.ajax_facets.firstLoad) { + Drupal.ajax_facets.reactOnStateChange(); + } + }); })(jQuery);