diff --git a/js/ajax_view.js b/js/ajax_view.js index d7fa0d7e..8a65ea25 100644 --- a/js/ajax_view.js +++ b/js/ajax_view.js @@ -8,45 +8,69 @@ * Attaches the AJAX behavior to Views exposed filter forms and key View links. */ Drupal.behaviors.ViewsAjaxView = {}; -Drupal.behaviors.ViewsAjaxView.attach = function() { +Drupal.behaviors.ViewsAjaxView.attach = function(context, settings) { if (Drupal.settings && Drupal.settings.views && Drupal.settings.views.ajaxViews) { - $.each(Drupal.settings.views.ajaxViews, function(i, settings) { - Drupal.views.instances[i] = new Drupal.views.ajaxView(settings); + $.each(Drupal.settings.views.ajaxViews, function(i, viewSettings) { + Drupal.views.instances[i] = new Drupal.views.ajaxView(viewSettings); }); - if (window.location.hash) { - hash = Drupal.Views.getLocationHash(); - - // Do we have a hash that corresponds to an auto-submit form? - $exposed_form_submitted = $('form#views-exposed-form-'+ hash.f); + // This attach function is invoked by two or more events: + // 1) First page load (context == document) + // 2) AJAX response (context == modified content) + // + // The views-auto-submit-processed class is applied by the $('body').once + // call in the submitExposedForm method, and should be cleared on every + // attach to prepare the environment for the user navigating back in + // history. + $('body').removeClass('views-auto-submit-processed'); + if (context == document) { + this.submitExposedForm(); + $(window).bind('popstate', jQuery.proxy(this.viewsHistoryCallback, this)); + } + } +}; - $lastLinkEvent = location.hash; +Drupal.behaviors.ViewsAjaxView.viewsHistoryCallback = function(event) { + this.submitExposedForm(); +} + +Drupal.behaviors.ViewsAjaxView.submitExposedForm = function() { + var curFormState = history.state; + if (!$.isEmptyObject(curFormState)) { + // Was an exposed form submitted? + exposedFormSubmitted = $('form#views-exposed-form-' + curFormState.f); + + // Ensure that the form is re-submitted once + $('body').once('views-auto-submit', function() { + if (Drupal.Views.isNumeric(curFormState.p)) { + var page = $('').val(curFormState.p); + exposedFormSubmitted.prepend(page); + } - $('body').once('views-hash', function() { - if (Drupal.Views.isNumeric(hash.p)) { - var pageValue = $('').val(hash.p); - $exposed_form_submitted.prepend(pageValue); - } - for (var v in hash) { - if (hash.hasOwnProperty(v)) { - // Set the form element to the value from the hash. - if (hash[v] == 'true') { - $exposed_form_submitted.find(('[id='+ v +']')).prop('checked', true); + for (var v in curFormState) { + if (curFormState.hasOwnProperty(v)) { + // Set the form element to the value from the query string + if (curFormState[v] == 'true') { + exposedFormSubmitted.find(('[id=' + v + ']')).prop('checked', true); + } + else { + if (curFormState[v] == 'false') { + exposedFormSubmitted.find(('[id=' + v + ']')).prop('checked', false); } else { - $exposed_form_submitted.find(('[id='+ v +']')).prop('value', hash[v]); + exposedFormSubmitted.find(('[id=' + v + ']')).prop('value', curFormState[v]); } } } + } - if ($exposed_form_submitted.hasClass('ctools-auto-submit-full-form')) { - $exposed_form_submitted.find('input, select').first().change(); - } - else { - $exposed_form_submitted.find(':submit').click(); - } - }); - } // End if hash. + if (exposedFormSubmitted.hasClass('ctools-auto-submit-full-form')) { + exposedFormSubmitted.find('input, select').first().change(); + } + else { + exposedFormSubmitted.find('#' + curFormState.submit).click(); + } + }); } }; @@ -118,44 +142,65 @@ Drupal.views.ajaxView = function(settings) { }; /** - * Exposed forms use this to append their id as a URL hash so we can esubmit the - * form when the browser's "Back" button is used. + * Store the current form state into the History queue, so that it is + * retrievable when interacting with the Browser's history. */ Drupal.views.ajaxView.prototype.ajaxCompleteExposedCallback = function(event, request, options) { if (options.url === this.element_settings.url) { - var data = Drupal.Views.parseQueryString(options.data); - hash = Drupal.Views.getLocationHash(); - - var val = {}; - var changed_exposed_filter = false; - this.$exposed_form.find('select, input').each(function(i, element) { - if ((this.name !== "" && this.type == 'select-one') || (this.type == 'checkbox')) { - changed_exposed_filter = true; - if (this.type == 'checkbox') { - if (this.checked) { - val[this.id] = 'true'; - } - else { - if (hash[this.id] == 'true') { - val[this.id] = 'false'; - } - } - } - else { - val[this.id] = this.value; - } + // WARNING: This method will be invoked by all ajaxView instances on a page, + // and we only want to store history for the submitted exposed form of a + // single view. The submitted exposed form ID and related information is + // extracted from the AJAX request. + var requestParams = Drupal.Views.parseQueryString(options.data); + var formState = $.isEmptyObject(history.state) ? {} : history.state; + var formStateChanged = false; + var page = requestParams.hasOwnProperty('page') ? requestParams.page : 0; + var formId = requestParams.view_name.replace(/_/g, '-') + + '-' + + requestParams.view_display_id.replace(/_/g, '-'); + var exposedForm = $('form#views-exposed-form-' + formId); + var submitId = 'edit-submit-' + requestParams.view_name.replace(/_/g, '-'); + + // When a View filter is updated, multiple Ajax requests are sent, and + // we cannot be sure that the View HTML is in sync with the final result of + // the Ajax request. Since the request parameters store the form values + // submitted in the Ajax request but *not* the HTML form element IDs, and + // retrieving the Ajax request parameters is more reliable than retrieving + // the HTML form values, the Ajax request parameters will be auto-associated + // with the HTML form values. + $(exposedForm).find('select, input').each(function(i, element) { + if (!this.hasAttribute('id') || this.id == '') { + return; } - else { - val[this.id] = this.value; + + var requestParamId = this.id.replace(/^edit-/, '').replace(/-/g, '_'); + if (requestParams.hasOwnProperty(requestParamId)) { + if (!formState.hasOwnProperty(this.id) || formState[this.id] != requestParams[requestParamId]) { + formState[this.id] = requestParams[requestParamId]; + formStateChanged = true; + } } }); - if (changed_exposed_filter) { - val['p'] = 0; + if (!formState.hasOwnProperty('p') || formState['p'] != page) { + formState['p'] = page; + formStateChanged = true; + } + + if (!formState.hasOwnProperty('f') || formState['f'] != formId) { + formState['f'] = formId; + formStateChanged = true; + } + + if (!formState.hasOwnProperty('submit') || formState['submit'] != submitId) { + formState['submit'] = submitId; + formStateChanged = true; + } + + if (formStateChanged) { + history.pushState(formState, ''); } - Drupal.Views.updateLocationHash(val); - Drupal.Views.updateLocationHash({ f: this.settings.view_name.replace(/_/g, '-') + '-' + this.settings.view_display_id.replace(/_/g, '-'), p: data.page}); this.$exposed_form.find('input[name=page]').remove(); } }; @@ -205,20 +250,6 @@ Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function(id, link) { this.element_settings.submit = viewData; this.pagerAjax = new Drupal.ajax(false, $link, this.element_settings); this.links.push(this.pagerAjax); - - // Attach click handler to update hash. - $link.click(this.pagerHandler); -}; - -/** - * Click handler updates the location hash based on pager. - */ -Drupal.views.ajaxView.prototype.pagerHandler = function(event) { - var args = Drupal.Views.parseQueryString(event.target.href); - if (typeof args.page === "undefined") { - args.page = 0; - } - Drupal.Views.updateLocationHash({ p: args.page }); }; Drupal.ajax.prototype.commands.viewsScrollTop = function (ajax, response, status) { diff --git a/js/base.js b/js/base.js index ac149235..5220b7ef 100644 --- a/js/base.js +++ b/js/base.js @@ -113,26 +113,6 @@ Drupal.Views.getPath = function (href) { }; /** - * Helper function updates window.location.hash data from a javascript object. - * - * We provide for multiple arbitrary values to be stored in the hash by using a - * urlencoded string. - */ -Drupal.Views.updateLocationHash = function (data) { - hash = Drupal.Views.getLocationHash(); - $.extend(hash, data); - window.location.hash = decodeURIComponent($.param(hash)); -}; - -/** - * Helper function to get the window.location.hash data. - */ -Drupal.Views.getLocationHash = function () { - hashStr = window.location.hash.substr(1); // Strips the # itself. - return Drupal.Views.parseQueryString(hashStr); -}; - -/** * Determines whether its argument represents a Javascript number (This is * provided in jQuery 1.7, but we're not there yet). */