diff --git a/ajax_facets.install b/ajax_facets.install index e69de29..74f3111 100644 --- a/ajax_facets.install +++ b/ajax_facets.install @@ -0,0 +1,41 @@ + $t('Ajax Facets'), + 'description' => $description, + 'value' => $value, + 'severity' => REQUIREMENT_INFO, + ); + break; + } + + return $requirements; +} diff --git a/ajax_facets.module b/ajax_facets.module index 6be08ae..e871353 100644 --- a/ajax_facets.module +++ b/ajax_facets.module @@ -93,6 +93,7 @@ function ajax_facets_add_ajax_js($facet) { static $included = FALSE; if (!$included) { $included = TRUE; + $history_js_exists = FALSE; $module_path = drupal_get_path('module', 'ajax_facets'); drupal_add_js($module_path . '/misc/ajax_facets.js'); drupal_add_css($module_path . '/misc/ajax_facets.css'); @@ -104,6 +105,16 @@ function ajax_facets_add_ajax_js($facet) { $view_name = ''; $display_name = ''; + // Add history.js file if exists. + if (module_exists('libraries')) { + $history_js_path = libraries_get_path('history.js'); + + if ($history_js_path) { + $history_js_exists = TRUE; + drupal_add_js($history_js_path . '/scripts/bundled/html4+html5/jquery.history.js', array('group' => JS_LIBRARY)); + } + } + if (!empty($views)) { // Get display from current search. if (empty($view_name) || empty($display_name)) { @@ -125,6 +136,7 @@ function ajax_facets_add_ajax_js($facet) { 'display_name' => $display_name, 'facet_field' => $facet['map options']['field']['key'], 'applyPath' => url($search_path, array('query' => $query)), + 'isHistoryJsExists' => $history_js_exists, ); drupal_add_js($setting, 'setting'); drupal_add_library('system', 'drupal.ajax'); diff --git a/misc/ajax_facets.js b/misc/ajax_facets.js index 731d72c..2e3f8b2 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.ajax_facets.getFacetsQueryUrl(Drupal.settings.basePath + Drupal.settings.pathPrefix + Drupal.settings.facetapi.searchPath), + 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,133 @@ } } } + + /** + * Returns query string with selected facets. + */ + Drupal.ajax_facets.getFacetsQueryUrl = function(baseUrl) { + 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; + } + + // Add query string to base url. + if (!$.isEmptyObject(query)) { + baseUrl += '?' + decodeURIComponent($.param(query)); + } + + return baseUrl; + }; + + /** + * 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) { + Drupal.ajax_facets.firstLoad = false; + + // If history.js available - use it. + if (Drupal.settings.facetapi.isHistoryJsExists) { + History.replaceState({ + current_id: $facet.attr('id'), + current_facet_name: $facet.data('facet'), + facets: Drupal.ajax_facets.queryState['f'] + }, null, null); + } else { + // Fallback to HTML5 history object. + if (typeof history.replaceState != 'undefined') { + history.replaceState({ + current_id: $facet.attr('id'), + current_facet_name: $facet.data('facet'), + facets: Drupal.ajax_facets.queryState['f'] + }, null, null); + } + } + } + }; + + /** + * 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 bind again. + */ + Drupal.ajax_facets.pushState = function (state, title, stateUrl) { + // If history.js available - use it. + if (Drupal.settings.facetapi.isHistoryJsExists) { + var $window = $(window); + + $window.unbind('statechange', Drupal.ajax_facets.reactOnStateChange); + History.pushState(state, title, stateUrl); + $window.bind('statechange', Drupal.ajax_facets.reactOnStateChange); + } else { + // Fallback to HTML5 history object. + if (typeof history.pushState != 'undefined') { + history.pushState(state, title, stateUrl); + } + } + }; + + /** + * Callback for back/forward browser buttons. + */ + Drupal.ajax_facets.reactOnStateChange = function () { + var state = null, + facets = [], + current_id = ''; + + // If history.js available - use it. + if (Drupal.settings.facetapi.isHistoryJsExists) { + state = History.getState(); + + facets = state.data.facets; + current_id = state.data.current_id; + } else { + // Fallback to HTML5 history object. + if (typeof history.pushState != 'undefined') { + state = history.state; + + facets = state.facets; + current_id = state.current_id; + } + } + + Drupal.ajax_facets.queryState['f'] = facets; + Drupal.ajax_facets.sendAjaxQuery($('#' + current_id), false); + }; + + // If user opened new page and then clicked browser's back button then would not be fired "statechange" event. + // So we need to bind on 'statechange' event and react only once. All farther work does + // Drupal.ajax_facets.pushState() function. + // If history.js Adapter available - use it to bind "statechange" event. + if (typeof History.Adapter != 'undefined') { + History.Adapter.bind(window, 'statechange', function () { + if (Drupal.ajax_facets.firstLoad) { + Drupal.ajax_facets.reactOnStateChange(); + } + }); + } else { + // Fallback to default HTML5 event. + window.onpopstate = function () { + if (Drupal.ajax_facets.firstLoad) { + Drupal.ajax_facets.reactOnStateChange(); + } + }; + } })(jQuery);