I believe we need two things to happen here:

  1. We can limit our support to the core shipping customer profile. We should respond to the onblur event for elements of the shipping address field. We can then validate and save the data to the shipping customer profile and recalculate shipping. This will allow shipping service rate callbacks to take the actual shipping address into consideration.
  2. We'll need a button that can be clicked to manually recalculate shipping. It should be able to save / update the shipping customer profile as well.
CommentFileSizeAuthor
#75 interdiff.txt913 bytesandyg5000
#75 commerce_shipping-support_recalculating_shipping_when_the_address_is_entered-1287124.73.patch2.51 KBandyg5000
#72 Screenshot 2015-04-16 23.06.23.png48.56 KBandyg5000
#72 commerce_shipping-support_recalculating_shipping_when_the_address_is_entered-1287124.72.patch2.38 KBandyg5000
#67 interdiff-commerce_shipping-287124-62-63.txt2.45 KBxurizaemon
#65 interdiff-commerce_shipping-287124-62-63.txt2.2 KBxurizaemon
#63 commerce_shipping-support_recalculating_shipping_when_the_address_is_entered-1287124.63.patch3.76 KBbgilhome
#62 commerce_shipping-support_recalculating_shipping_when_the_address_is_entered-1287124.60.patch2.2 KBandyg5000
#60 commerce_shipping-support_recalculating_shipping_when_the_address_is_entered-1287124.60.patch1.41 KBandyg5000
#55 recalculate-shipping-jquery-selectors-1287124-46.patch1.41 KBandyg5000
#46 recalculate-shipping-jquery-selectors-1287124-45.patch1.57 KBjessepinho
#45 recalculate-shipping-jquery-selectors-1287124-45.patch1.51 KBjessepinho
#40 commerce_illegal_choice.patch1.18 KBdelta
#36 commerce_shipping-illegal_choice.patch742 bytesfranz
#30 commerce_shipping-recalulate_shipping_dynamically-1287124-30.patch9.34 KBcvangysel
#27 commerce_shipping-recalulate_shipping_dynamically-1287124-27.patch8.79 KBcvangysel
#23 checkout-shipping-errors.png54.71 KBrszrama
#18 commerce_shipping-recalulate_shipping_dynamically-1287124-18.patch1.44 KBcvangysel
#10 commerce_shipping-recalulate_shipping_dynamically-1287124-7.patch6.68 KBandyg5000
#6 recalculate-shipping-1287124-6.patch7.24 KBhelior
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

bojanz’s picture

:)

atlea’s picture

Why not use a separate checkout pane for shipping?

rszrama’s picture

That's what we do. : P

emptyvoid’s picture

I agree, if at least an "update" button was provided to provide an Ajax driven update of the shipping methods and shipping options panels it would go a long way to making the Addresses and Shipping options some what integrated.

Does anyone have recommendations on blogs, books to read about the AJAX framework and or triggers for behaviors and panels in the checkout page?

googletorp’s picture

Status: Active » Postponed

Not planning on working on this, but leaving it in the issue queue in case some one wants to.

helior’s picture

Status: Postponed » Needs review
FileSize
7.24 KB

This patch adds a setting in the shipping information checkout pane to let the shipping information pane dynamically interact with the shipping services pane.

- It attempts to re-calculate shipping as values are changing in the shipping address (but only once all required field values are available)
- Is only invoked when both the shipping service and shipping information panes are together on the same checkout page.
- There's a manual "Re-calculate shipping" button in there, too.

rszrama’s picture

Status: Needs review » Needs work

Oooh, great start, Helior. I think we'll need to do a little clean-up on this, but I like the way you're thinking. Some thoughts:

  1. I think we need to move the setting and the "recalculate" button to the shipping service selection pane itself. (Ahh, and minor, but recalculate is an actual word, so we don't have to hyphenate it.)
  2. Additionally, we can just hide the setting of the two panes aren't on the same page and make it the default behavior to perform the recalculation.
  3. We should consider hiding the "recalculate" button via JavaScript.
  4. I noticed that if I add a value to a form element (say the Zip code, since it's the last one) and then submit the form without first blurring off that form element first, I get a JavaScript error alert box. Not sure why, but I've seen it in autocomplete textfields before and we'll want to avoid that.
  5. I'd like to get a better handle on how this is affecting order data, as it seems to magically create a customer profile once you've filled in all the required fields on the addressfield widget. However, say I've added another field to the customer profile type, such as a required phone number field. Since I don't have all the required data, it won't create the customer profile after I finish the address, so I'm forced to click the recalculate button. I wonder if we should just add the AJAX triggered recalculation to every required element in the shipping information checkout pane, and maybe we can add some client side JavaScript to prevent the AJAX request until all required fields have been filled in. Anything there you think I'm missing?
arun_ms’s picture

hello all,

I applied the patch. but recalculating checkbox didnt display for me.
what should i do? also does this update "cart summary block" in same checkout page(one page checkout) ?

Thanks

rszrama’s picture

Please notice in comment #7 that this patch needs work. No reason for us to support it in the meantime. Also, there's a separate issue in the queue for you final question.

andyg5000’s picture

Status: Needs work » Needs review
FileSize
6.68 KB

Here's an update to the patch that solves Ryan's questions by doing the following:

1. Moved the recalculate button to the shipping services pane and moved the setting to the shipping pane settings page.
2. Hide the settings option if the two panes aren't on the same page. Set the default to TRUE.
3. The recalculate button is hidden using CSS (element-invisible) so that it can be made visible to the user if rates are required but cannot be calculated.
4. The recalculate button uses JS validation to make sure that all required shipping pane form fields are complete before firing callback.
5. Since I've implemented JS validation, new fields that are required are automatically added to the validation so this is no longer an issue.

I'm far from a JS expert, so I'm sure that will need some work.

To do:
Attach recalculate callback to addressbook callback chain so that it's fired when the address is changed
Fire recalculate callback when copying billing profile > shipping profile

googletorp’s picture

Thanks for your work on this andyg5000. I've been crazy busy in the weeks before Drupal con and probably won't get much time to look at it during. But just wanted to say that I have noticed your patch and I do want to give it the reviews it deserves.

I hope Ryan will look at this as well, since he has done most of the work on the 2.x branch.

googletorp’s picture

Assigned: Unassigned » rszrama
Status: Needs review » Needs work

I'm going to mark this as need work.

The gist of it, is that the patch works pretty well for shipping, though there are some caching issues. The problem is, however, the patch doesn't support copy address functionality. This isn't too bad in itself, but the architecture of the patch makes it impossible to incorporate the changes needed to make it work if copy address functionality is enabled.

Ryan was going to take a look at it in the flight home so I'm assigning the issue to him. I don't know how far he got - so waiting to hear from him until we do anything else.

andyg5000’s picture

@googletorp, Thanks for the review. I agree that the patch I submitted is not scaleable. After brainstorming this and looking at commerce addressbook, I think the ability to alter the AJAX response commands is going to be necessary to do what we want here. I've created an issue and patch on commerce #1759494: Add ability to alter AJAX callback during address copy that would allow more AJAX commands to be executed. My thought is that we can chain the same actions used to update shipping fields (on blur) to this callback and reload the shipping services pane.

rszrama’s picture

fwiw, I'd already added this into my work on the plane, just hadn't had a chance to update this issue since landing. Will get back to you this afternoon. ; )

rszrama’s picture

Just a quick update here - I've already made the commits to core to change the profile copying feature to support what needs to happen here. I've been moving this code around here and fleshing it out to support address copying and any customer profile type. The goal is that any change to a customer profile form element could trigger shipping recalculation so long as all customer profile fields have been filled in. The last bit I need to update is the .js, as the module code has been updated to support this. Makes it a bit hard to post an updated patch since the .js file isn't already under source control, so I'm just going to hit the sack and get back at it in the morning.

fwiw, I had a heck of a time figuring out why I wasn't getting the data I needed in $form_state['values'] to copy the profile data manually when necessary. Turns out it was being stripped out because of #limit_validation_errors, which was only processing $form_state['input'] for the parts of the array we explicit told it to in the #limit_validation_errors array on the recalculation button. Updated that array to allow for any customer profile type on the form to be validated, and voila! It worked.

Then had to monkey around with the $form_state['addressfield'] array, because it was retaining old data even after performing the manual copy and saving the customer profiles.

Last bit now server side is to figure out how to ensure we have a default shipping option selected when the services form rebuilds.

rszrama’s picture

Assigned: rszrama » Unassigned
Status: Needs work » Fixed

Ahh, as it turns out I already had updated the JavaScript, but I retooled it a bit anyways to use a jQuery function to check whether or not to recalculate shipping so we could use ajax_command_invoke() in conjunction with the handy new hook we added to Commerce core. ; )

I've tested this as well as I can, and it works with different services being available and/or rate calculation rules firing on different address related conditions. We'll need to bang on this a little more, but just make sure you do your testing with the latest Commerce 1.x-dev.

Commit: http://drupalcode.org/project/commerce_shipping.git/commitdiff/708a8e3

D'oh! I just realized I forget to include helior in the commit credits. : (

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

cvangysel’s picture

Status: Closed (fixed) » Needs review
FileSize
1.44 KB

I encountered an issue where the Ajax was launched before the form had a chance to re-organize itself, this caused some weird and inconsistent behaviour. The following patch fixes this by checking if the customer profile form is still processing.

I also removed the checks for empty values. The errors are now shown inside the shipping services panel.

cvangysel’s picture

Status: Needs review » Needs work

There are still other problems with this too. Mainly when javascript is disabled, I'll investigate tomorrow.

rszrama’s picture

fwiw, the checkout form isn't degradable right now; I wouldn't consider that a blocking issue for Shipping, but it's definitely something we need to fix in a subsequent Commerce release.

Kris D.’s picture

hi,
for me work perfect #16 with the #18 patch.
But now the problem is that the subtotal and total not update. Maybe it will be interesting to update these values by ajax too. Somebody know the way.

thanks for all

Zac_JH’s picture

+1 for patch in #18, 2 days of trying to figure out the problem. Now working just lovely, thank you cvangysel

rszrama’s picture

FileSize
54.71 KB

I don't think we can move forward without doing some sort of client-side validation. It doesn't make sense to start showing errors in the "Shipping service" checkout pane just because someone put their name in an address field. The purpose of the checks is to not attempt to recalculate shipping automatically until we're assured we have the necessary data to save the customer profiles / perform the calculation.

It can result in some pretty unhelpful error messages otherwise:

Can you help me understand how you triggered the broken behavior? I'm not sure I can reproduce it just with the description given.

cvangysel’s picture

You're right, but I'm not too sure about showing the shipping options all the time.

I was personally thinking of showing a message in the shipping services pane that says all forms should be completed before you're able to select a shipping service. I'll look into this later today.

Zac_JH’s picture

I've been working on this a lot trying to get towards an effective single page checkout. I've been testing an alternate selector for initiating the action to trigger the JS click on shipping recalculate (in commerce_shipping.js).

$('#edit-customer-profile-shipping [id^="addressfield-wrapper"] .form-wrapper:last div:last input').change(function() { ...

This should remove the error messages Ryan describes as the shipping code will not be run until the last form element in the address has been completed. It could come undone if the last element is not "required", but I made all shipping address elements required.

cvangysel’s picture

Assigned: Unassigned » cvangysel

I'm actually working on a patch to get this fixed.

Forgot to assign to myself, will post soon.

cvangysel’s picture

Status: Needs work » Needs review
FileSize
8.79 KB

This patch silently validates the address information. It then submits and stores the customer profile, then it shows the available shipping services. If the customer profile fields aren't complete, the user will not be able to select a shipping service.

joshmiller’s picture

Status: Needs review » Needs work

cvangysel ...

I did the following at the request of rszrama:

1) I applied the patch to a clean Commerce 1.x dev, Flat Rate 1.x dev, and Shipping 2.x dev ...
2) Created two flat rates
3) Modified the components to only show up on specific states
4) Created a calculation rule that would multiply shipping by 300 if you were from Louisville
5) Moved Billing and Shipping Address panes to Shipping Pane
6) Made shipping copy from billing
7) Added a product and checked out

When I entered my billing information, the shipping pane tried to update, no errors, but the shipping remained empty. After changing my state / city a couple of times, it was clear shipping was not able to get to my billing customer profile (that should be copied as a shipping customer profile).

So I un-clicked the "copy my shipping information from billing." Once I filled it in, the shipping method was coming in and the calculation rule was firing.

To confirm, I checked out a second time and this time (before even entering the billing information) clicked and unclicked the checkbox. The shipping methods came in without a problem while adding the billing information.

To double confirm, I reversed the scenario and made shipping default and billing a "copy from shipping" checkbox. The same error was not seen here.

Ryan thought this was because the shipping customer profile needed to be created, even if it was empty, for the update to understand what was happening.

cvangysel’s picture

I'll have another look at this today and tomorrow.

cvangysel’s picture

Status: Needs work » Needs review
FileSize
9.34 KB

I encountered the same problem as you were having and this patch should solve that problem.

brephraim’s picture

Patch failed for me running beta1+19-dev.

error: while searching for:
    $pane_form['message'] = array(
      '#markup' => '<div>' . t('Unfortunately we could not calculate any valid shipping rates for your order, and we require shipping service selection to complete checkout.') . '<br /><strong>' . t('Please contact us to resolve any issues with your order.') . '</strong></div>',
    );

    $pane_form['shipping_rates'] = array(
      '#type' => 'value',
      '#value' => FALSE,
    );
  }

  return $pane_form;

error: patch failed: includes/commerce_shipping.checkout_pane.inc:127
error: includes/commerce_shipping.checkout_pane.inc: patch does not apply
cvangysel’s picture

#30 was created against the 7.x-2.x branch.

brephraim’s picture

Yes, forgive me, I should have specified: 7.x-2.0-beta1+19-dev, which is the latest dev.

rszrama’s picture

Just ran a review of this patch, and all appears to work fine. I'm going to read over the resultant code once more and hopefully commit tomorrow or Monday. It's about time we had a 2.0. : P

rszrama’s picture

Status: Needs review » Fixed

Alrighty, so I really dug into the JS and noticed two things:

  1. I was getting excessive amounts of calls to the recalculation function when changing address elements.
  2. The .required check was actually not stopping recalculation when it should.

It took me several hours to track down the solution to #1, because there were plenty of red herrings for me to follow along the way. It wasn't until I was driving home from lunch (at 5 PM, b/c I sat there all afternoon pounding my head against this ; ) that I realized the number of calls to that function was increasing with each change. I then remembered that we weren't including a check in our Drupal behavior code to prevent reattaching the behavior, typically mitigated through some sort of "processed" class. Added that in and all was well with the world. This bug existed even before the patch, so it's my own crap code coming back to bite me. ; )

The fix for #2 was a lot faster. I couldn't figure out why the return; inside the .each() wasn't halting the recalculation process. Eventually I realized it was because we were simply returning from the anonymous function used in the .each() loop, not returning out of the recalculation function itself. I reverted to the approach I had pre-patch #30 and simply set a variable to determine whether or not recalculation should proceed. All better.

I read through the rest of the code and am pretty sure I understand the rationale behind the changes. I have no problems with it, though it is a bit of a degradation to no longer show available shipping options on the first pageload. Not a huge deal, though, since we're basically assuming address information is required if recalculation is turned on. I've updated the setting's help text to make this clear, and I also made the message that shows on the initial pageload a little more generic.

Great job over all. I'm happy to commit this and knock one more barrier to a Shipping 2.0 down. : )

Commit: http://drupalcode.org/project/commerce_shipping.git/commitdiff/bc0109d

franz’s picture

Status: Fixed » Needs review
FileSize
742 bytes

I found an issue here. Some fields were added with ajax callbacks themselves. When their ajax call was fired, the options for shipping services were changed. A moment later the Recalculate ajax is fired, but cause the "illegal" error as it submits a form that is no longer valid, but the options were not replaced by the new ones.

My patch is a workaround to avoid the bug, it simply de-select any option before recalculating.

rszrama’s picture

Yeah, it sounds like a deeper issue to me. If the options were changed, this should be reflected in what's shown on the form so that illegal options may not be submitted. I don't really understand what you have going on, though. Can you post screenshots and/or the code?

illmatix’s picture

Subscribed +1

Zac_JH’s picture

Hi guys

This still seems to be missing an important part if you're using a single page checkout. Which is the basket needs to be recalculated too.

The simple fix for this if anyone is looking for it is to replace in commerce_shipping/includes/commerce_shipping.checkout_pane.inc

/**
* Ajax callback: Returns recalculated shipping services.
*/
function commerce_shipping_recalculate_services_refresh($form, $form_state) {
return $form['commerce_shipping'];
}

WITH

/**
* Ajax callback: Returns recalculated shipping services.
*/
function commerce_shipping_recalculate_services_refresh($form, $form_state) {
$commands[] = ajax_command_replace('#commerce-shipping-service-ajax-wrapper', drupal_render($form['commerce_shipping']));
$commands[] = ajax_command_invoke(NULL, 'commerceupdchckcrt', array());

return array('#type' => 'ajax', '#commands' => $commands);
}

AND add this JS through the normal drupal add js:

(function($){
$.fn.commerceupdchckcrt = function() {
if ($('#commerce-shipping-service-ajax-wrapper').find('.ajax-progress').length) {
return setTimeout($.fn.commerceupdchckcrt, 100);
}
// Trigger the reload of the cart
$('[id^="edit-cart-contents-basket-refresh"]').trigger('click');
}
})(jQuery);

Hope it helps some-one and possibly some-one more in the *know* can include this into shipping 2?

Thanks

delta’s picture

To reproduce the issue of illegal choice.
you need :

- 2 shipping services, with two rules condition based on order address component. One for france and one for others country.

- Go on the checkout page, assume france is selected by default. You have the shipping service for france selected in shipping pane.

- Change the country and submit the form before the ajax request for recalculation is done.

In the patch,
i disable the continue button until the recalculation is done to avoid error message.

Daemon_Byte’s picture

The update button does not appear to be on the checkout page so this code doesn't work for me :(

I also noticed that if the form does not pass the validation (ie all the required fields) it does not update the shipping info. This seems confusing to me as a person might change the country to get a rough price of delivery and be misled.

mglaman’s picture

This patch resolved my issues - we have US Domestic shipping (tiered) and a flat International shipping rate with United States set as default country.

International shipping is applied if country is not equal to US, and the tiered shipping is applied if country is equal US.

Due to the shipping services not being calculated until all the address fields are filled out customers are automatically shown International shipping costs and we actually had a few US residents get charged as International.

This patch prevents customers from "jumping the gun" and either causing an error or receiving mis-calculation.

googletorp’s picture

Assigned: cvangysel » rszrama

Ryan, can you take a look at this?

jessepinho’s picture

Couple issues I noticed with this:

- Themes that use different markup for form elements/wrappers break the logic of the jQuery selectors. For instance, I'm using the Mothership theme, which removes both the "required" class from required form items (since each .form-item that is required has a .form-required class, anyway), as well as the .form-[type] class from form fields. Similarly, other themes could eliminate the .form-item wrapper altogether.
- As far as I can tell, there's no graceful degradation provided if JS is disabled, meaning users without JS can skip paying for shipping completely if they go through the checkout process without ever clicking "Recalculate shipping", if the "Require a shipping service at all times, preventing checkout if none are available" option is not checked. While site admins can simply check this option, shipping may not apply to some products. (Should this be a separate issue? There are already a bunch of issues related to shippable/nonshippable products; perhaps this really falls under that category.)

I'll submit a patch for the first issue ASAP.

jessepinho’s picture

Patch attached. Note that, instead of selecting .form-item's children(), I simply selected all :input elements inside of '[id^="edit-customer-profile"]'. This allows for a simpler selector and solves the theming issue.

As for the .required selector: with the change to line 39, it is no longer required that the theme use .form-item wrappers. Also, note that if a theme does not use .required for required form elements, recalculate will never be set to false, which simply means that shipping will be recalculated every time a form element inside the shipping profile is changed. This is not exactly ideal, but functional—and anyway, most themes still use the .required class for required form elements, so it shouldn't be a problem.

Note, too, that the Mothership theme adds the .required class to the <label>s for required form elements. As a result, recalculate will ALWAYS be set to false, since $(label).val() will always be equal to the empty string. Since this patch selects only :input elements, no <label> elements will be selected.

jessepinho’s picture

Rerolled (see the new line 39). In the last patch, only :inputs with the .required class were selected. Now, the HTML5-friendly [required] selector is also used. (This would be more useful if Drupal core used HTML5; but for now, this will work with both default Drupal themes and HTML5 themes.)

primozsusa’s picture

Shipping service Ajax checbox: "Calculation is only triggered when all required fields have been entered and the shipping services pane is visible on the page."

This works ok if you are filling the form and exiting the form to submit button with "tab" or clicking outside of the form. But if you click directly the "continue to next step" button then ajax recalculation does not happen.

"commerce_shipping 7.x-2.0"

IckZ’s picture

This works ok if you are filling the form and exiting the form to submit button with "tab" or clicking outside of the form. But if you click directly the "continue to next step" button then ajax recalculation does not happen.

same here!

mibfire’s picture

Issue summary: View changes

It would be great when ajax is running the "continue to next step" button couldnt be clicked or if clicked it would wait for the ajax complete and then make a manual click on the submit button. Cos if ajax is running and you try to hit the submit you will get an error that says ajax http request was aborted. Moreover I dont understand why the shipping service is refreshed if billing address is changed.

IckZ’s picture

Hey there,
temp. I'm in beta testing with a modified/hooked misc/ajax.js

template.php <-- replace THEME_NAME (twice :))!!

/**
  * Perform necessary alterations to the JavaScript before it is presented on the page. 
  */

function THEMA_NAME_js_alter(&$javascript) {
  if (isset($javascript['misc/ajax.js'])) {
    $javascript['misc/ajax.js']['data'] = drupal_get_path('theme', 'THEME_NAME') . '/js/ajax_core_rewrite.js';
  }
}
 

and place this file under theme_name/js/ajax_core_rewrite.js

(function ($) {

/**
 * Provides Ajax page updating via jQuery $.ajax (Asynchronous JavaScript and XML).
 *
 * Ajax is a method of making a request via JavaScript while viewing an HTML
 * page. The request returns an array of commands encoded in JSON, which is
 * then executed to make any changes that are necessary to the page.
 *
 * Drupal uses this file to enhance form elements with #ajax['path'] and
 * #ajax['wrapper'] properties. If set, this file will automatically be included
 * to provide Ajax capabilities.
 */

Drupal.ajax = Drupal.ajax || {};

/**
 * Attaches the Ajax behavior to each Ajax form element.
 */
Drupal.behaviors.AJAX = {
  attach: function (context, settings) {
    // Load all Ajax behaviors specified in the settings.
    for (var base in settings.ajax) {
      if (!$('#' + base + '.ajax-processed').length) {
        var element_settings = settings.ajax[base];

        if (typeof element_settings.selector == 'undefined') {
          element_settings.selector = '#' + base;
        }
        $(element_settings.selector).each(function () {
          element_settings.element = this;
          Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
        });

        $('#' + base).addClass('ajax-processed');
      }
    }

    // Bind Ajax behaviors to all items showing the class.
    $('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () {
      var element_settings = {};
      // Clicked links look better with the throbber than the progress bar.
      element_settings.progress = { 'type': 'throbber' };

      // For anchor tags, these will go to the target of the anchor rather
      // than the usual location.
      if ($(this).attr('href')) {
        element_settings.url = $(this).attr('href');
        element_settings.event = 'click';
      }
      var base = $(this).attr('id');
      Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
    });

    // This class means to submit the form to the action using Ajax.
    $('.use-ajax-submit:not(.ajax-processed)').addClass('ajax-processed').each(function () {
      var element_settings = {};

      // Ajax submits specified in this manner automatically submit to the
      // normal form action.
      element_settings.url = $(this.form).attr('action');
      // Form submit button clicks need to tell the form what was clicked so
      // it gets passed in the POST request.
      element_settings.setClick = true;
      // Form buttons use the 'click' event rather than mousedown.
      element_settings.event = 'click';
      // Clicked form buttons look better with the throbber than the progress bar.
      element_settings.progress = { 'type': 'throbber' };

      var base = $(this).attr('id');
      Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings);
    });
  }
};

/**
 * Ajax object.
 *
 * All Ajax objects on a page are accessible through the global Drupal.ajax
 * object and are keyed by the submit button's ID. You can access them from
 * your module's JavaScript file to override properties or functions.
 *
 * For example, if your Ajax enabled button has the ID 'edit-submit', you can
 * redefine the function that is called to insert the new content like this
 * (inside a Drupal.behaviors attach block):
 * @code
 *    Drupal.behaviors.myCustomAJAXStuff = {
 *      attach: function (context, settings) {
 *        Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) {
 *          new_content = $(response.data);
 *          $('#my-wrapper').append(new_content);
 *          alert('New content was appended to #my-wrapper');
 *        }
 *      }
 *    };
 * @endcode
 */
Drupal.ajax = function (base, element, element_settings) {
  var defaults = {
    url: 'system/ajax',
    event: 'mousedown',
    keypress: true,
    selector: '#' + base,
    effect: 'none',
    speed: 'none',
    method: 'replaceWith',
    progress: {
      type: 'throbber',
      message: Drupal.t('Please wait...')
    },
    submit: {
      'js': true
    }
  };

  $.extend(this, defaults, element_settings);

  this.element = element;
  this.element_settings = element_settings;

  // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let
  // the server detect when it needs to degrade gracefully.
  // There are five scenarios to check for:
  // 1. /nojs/
  // 2. /nojs$ - The end of a URL string.
  // 3. /nojs? - Followed by a query (with clean URLs enabled).
  //      E.g.: path/nojs?destination=foobar
  // 4. /nojs& - Followed by a query (without clean URLs enabled).
  //      E.g.: ?q=path/nojs&destination=foobar
  // 5. /nojs# - Followed by a fragment.
  //      E.g.: path/nojs#myfragment
  this.url = element_settings.url.replace(/\/nojs(\/|$|\?|&|#)/g, '/ajax$1');
  this.wrapper = '#' + element_settings.wrapper;

  // If there isn't a form, jQuery.ajax() will be used instead, allowing us to
  // bind Ajax to links as well.
  if (this.element.form) {
    this.form = $(this.element.form);
  }

  // Set the options for the ajaxSubmit function.
  // The 'this' variable will not persist inside of the options object.
  var ajax = this;
  ajax.options = {
    url: ajax.url,
    data: ajax.submit,
    beforeSerialize: function (element_settings, options) {
      return ajax.beforeSerialize(element_settings, options);
    },
    beforeSubmit: function (form_values, element_settings, options) {
      ajax.ajaxing = true;
      return ajax.beforeSubmit(form_values, element_settings, options);
    },
    beforeSend: function (xmlhttprequest, options) {
      ajax.ajaxing = true;
      return ajax.beforeSend(xmlhttprequest, options);
    },
    success: function (response, status) {
      // Sanity check for browser support (object expected).
      // When using iFrame uploads, responses must be returned as a string.
      if (typeof response == 'string') {
        response = $.parseJSON(response);
      }
      return ajax.success(response, status);
    },
    complete: function (response, status) {
      ajax.ajaxing = false;
      if (status == 'error' || status == 'parsererror') {
        return ajax.error(response, ajax.url);
      }
    },
    dataType: 'json',
    type: 'POST'
  };

  // Bind the ajaxSubmit function to the element event.
  $(ajax.element).bind(element_settings.event, function (event) {
    return ajax.eventResponse(this, event);
  });

  // If necessary, enable keyboard submission so that Ajax behaviors
  // can be triggered through keyboard input as well as e.g. a mousedown
  // action.
  if (element_settings.keypress) {
    $(ajax.element).keypress(function (event) {
      return ajax.keypressResponse(this, event);
    });
  }

  // If necessary, prevent the browser default action of an additional event.
  // For example, prevent the browser default action of a click, even if the
  // AJAX behavior binds to mousedown.
  if (element_settings.prevent) {
    $(ajax.element).bind(element_settings.prevent, false);
  }
};

/**
 * Handle a key press.
 *
 * The Ajax object will, if instructed, bind to a key press response. This
 * will test to see if the key press is valid to trigger this event and
 * if it is, trigger it for us and prevent other keypresses from triggering.
 * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13
 * and 32. RETURN is often used to submit a form when in a textfield, and 
 * SPACE is often used to activate an element without submitting. 
 */
Drupal.ajax.prototype.keypressResponse = function (element, event) {
  // Create a synonym for this to reduce code confusion.
  var ajax = this;

  // Detect enter key and space bar and allow the standard response for them,
  // except for form elements of type 'text' and 'textarea', where the 
  // spacebar activation causes inappropriate activation if #ajax['keypress'] is 
  // TRUE. On a text-type widget a space should always be a space.
  if (event.which == 13 || (event.which == 32 && element.type != 'text' && element.type != 'textarea')) {
    $(ajax.element_settings.element).trigger(ajax.element_settings.event);
    return false;
  }
};

/**
 * Handle an event that triggers an Ajax response.
 *
 * When an event that triggers an Ajax response happens, this method will
 * perform the actual Ajax call. It is bound to the event using
 * bind() in the constructor, and it uses the options specified on the
 * ajax object.
 */
Drupal.ajax.prototype.eventResponse = function (element, event) {
  // Create a synonym for this to reduce code confusion.
  var ajax = this;

  // Do not perform another ajax command if one is already in progress.
  if (ajax.ajaxing) {
    return false;
  }

  try {
    if (ajax.form) {
      // If setClick is set, we must set this to ensure that the button's
      // value is passed.
      if (ajax.setClick) {
        // Mark the clicked button. 'form.clk' is a special variable for
        // ajaxSubmit that tells the system which element got clicked to
        // trigger the submit. Without it there would be no 'op' or
        // equivalent.
        element.form.clk = element;
      }

      ajax.form.ajaxSubmit(ajax.options);
    }
    else {
      ajax.beforeSerialize(ajax.element, ajax.options);
      $.ajax(ajax.options);
    }
  }
  catch (e) {
    // Unset the ajax.ajaxing flag here because it won't be unset during
    // the complete response.
    ajax.ajaxing = false;
    alert("An error occurred while attempting to process " + ajax.options.url + ": " + e.message);
  }

  // For radio/checkbox, allow the default event. On IE, this means letting
  // it actually check the box.
  if (typeof element.type != 'undefined' && (element.type == 'checkbox' || element.type == 'radio')) {
    return true;
  }
  else {
    return false;
  }

};

/**
 * Handler for the form serialization.
 *
 * Runs before the beforeSend() handler (see below), and unlike that one, runs
 * before field data is collected.
 */
Drupal.ajax.prototype.beforeSerialize = function (element, options) {
  // Allow detaching behaviors to update field values before collecting them.
  // This is only needed when field values are added to the POST data, so only
  // when there is a form such that this.form.ajaxSubmit() is used instead of
  // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize()
  // isn't called, but don't rely on that: explicitly check this.form.
  if (this.form) {
    var settings = this.settings || Drupal.settings;
    Drupal.detachBehaviors(this.form, settings, 'serialize');
  }

  // Prevent duplicate HTML ids in the returned markup.
  // @see drupal_html_id()
  options.data['ajax_html_ids[]'] = [];
  $('[id]').each(function () {
    options.data['ajax_html_ids[]'].push(this.id);
  });

  // Allow Drupal to return new JavaScript and CSS files to load without
  // returning the ones already loaded.
  // @see ajax_base_page_theme()
  // @see drupal_get_css()
  // @see drupal_get_js()
  options.data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme;
  options.data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token;
  for (var key in Drupal.settings.ajaxPageState.css) {
    options.data['ajax_page_state[css][' + key + ']'] = 1;
  }
  for (var key in Drupal.settings.ajaxPageState.js) {
    options.data['ajax_page_state[js][' + key + ']'] = 1;
  }
};

/**
 * Modify form values prior to form submission.
 */
Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
  // This function is left empty to make it simple to override for modules
  // that wish to add functionality here.
};

/**
 * Prepare the Ajax request before it is sent.
 */
Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) {
  // For forms without file inputs, the jQuery Form plugin serializes the form
  // values, and then calls jQuery's $.ajax() function, which invokes this
  // handler. In this circumstance, options.extraData is never used. For forms
  // with file inputs, the jQuery Form plugin uses the browser's normal form
  // submission mechanism, but captures the response in a hidden IFRAME. In this
  // circumstance, it calls this handler first, and then appends hidden fields
  // to the form to submit the values in options.extraData. There is no simple
  // way to know which submission mechanism will be used, so we add to extraData
  // regardless, and allow it to be ignored in the former case.
  if (this.form) {
    options.extraData = options.extraData || {};

    // Let the server know when the IFRAME submission mechanism is used. The
    // server can use this information to wrap the JSON response in a TEXTAREA,
    // as per http://jquery.malsup.com/form/#file-upload.
    options.extraData.ajax_iframe_upload = '1';

    // The triggering element is about to be disabled (see below), but if it
    // contains a value (e.g., a checkbox, textfield, select, etc.), ensure that
    // value is included in the submission. As per above, submissions that use
    // $.ajax() are already serialized prior to the element being disabled, so
    // this is only needed for IFRAME submissions.
    var v = $.fieldValue(this.element);
    if (v !== null) {
      options.extraData[this.element.name] = v;
    }
  }

  // Disable the element that received the change to prevent user interface
  // interaction while the Ajax request is in progress. ajax.ajaxing prevents
  // the element from triggering a new request, but does not prevent the user
  // from changing its value.
  $(this.element).addClass('progress-disabled').attr('disabled', true);
  $('input.form-submit').addClass('progress-disabled').attr('disabled', true); // COREHACK HERE :)

  // Insert progressbar or throbber.
  if (this.progress.type == 'bar') {
    var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback));
    if (this.progress.message) {
      progressBar.setProgress(-1, this.progress.message);
    }
    if (this.progress.url) {
      progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
    }
    this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar');
    this.progress.object = progressBar;
    $(this.element).after(this.progress.element);
  }
  else if (this.progress.type == 'throbber') {
    this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber">&nbsp;</div></div>');
    if (this.progress.message) {
      $('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>');
    }
    $(this.element).after(this.progress.element);
  }
};

/**
 * Handler for the form redirection completion.
 */
Drupal.ajax.prototype.success = function (response, status) {
  // Remove the progress element.
  if (this.progress.element) {
    $(this.progress.element).remove();
  }
  if (this.progress.object) {
    this.progress.object.stopMonitoring();
  }
  $(this.element).removeClass('progress-disabled').removeAttr('disabled');
  $('input.form-submit').removeClass('progress-disabled').removeAttr('disabled'); // COREHACK HERE :)

  Drupal.freezeHeight();

  for (var i in response) {
    if (response.hasOwnProperty(i) && response[i]['command'] && this.commands[response[i]['command']]) {
      this.commands[response[i]['command']](this, response[i], status);
    }
  }

  // Reattach behaviors, if they were detached in beforeSerialize(). The
  // attachBehaviors() called on the new content from processing the response
  // commands is not sufficient, because behaviors from the entire form need
  // to be reattached.
  if (this.form) {
    var settings = this.settings || Drupal.settings;
    Drupal.attachBehaviors(this.form, settings);
  }

  Drupal.unfreezeHeight();

  // Remove any response-specific settings so they don't get used on the next
  // call by mistake.
  this.settings = null;
};

/**
 * Build an effect object which tells us how to apply the effect when adding new HTML.
 */
Drupal.ajax.prototype.getEffect = function (response) {
  var type = response.effect || this.effect;
  var speed = response.speed || this.speed;

  var effect = {};
  if (type == 'none') {
    effect.showEffect = 'show';
    effect.hideEffect = 'hide';
    effect.showSpeed = '';
  }
  else if (type == 'fade') {
    effect.showEffect = 'fadeIn';
    effect.hideEffect = 'fadeOut';
    effect.showSpeed = speed;
  }
  else {
    effect.showEffect = type + 'Toggle';
    effect.hideEffect = type + 'Toggle';
    effect.showSpeed = speed;
  }

  return effect;
};

/**
 * Handler for the form redirection error.
 */
Drupal.ajax.prototype.error = function (response, uri) {
  alert(Drupal.ajaxError(response, uri));
  // Remove the progress element.
  if (this.progress.element) {
    $(this.progress.element).remove();
  }
  if (this.progress.object) {
    this.progress.object.stopMonitoring();
  }
  // Undo hide.
  $(this.wrapper).show();
  // Re-enable the element.
  $(this.element).removeClass('progress-disabled').removeAttr('disabled');
  $('input.form-submit').removeClass('progress-disabled').removeAttr('disabled'); // COREHACK HERE :)

  // Reattach behaviors, if they were detached in beforeSerialize().
  if (this.form) {
    var settings = response.settings || this.settings || Drupal.settings;
    Drupal.attachBehaviors(this.form, settings);
  }
};

/**
 * Provide a series of commands that the server can request the client perform.
 */
Drupal.ajax.prototype.commands = {
  /**
   * Command to insert new content into the DOM.
   */
  insert: function (ajax, response, status) {
    // Get information from the response. If it is not there, default to
    // our presets.
    var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper);
    var method = response.method || ajax.method;
    var effect = ajax.getEffect(response);

    // We don't know what response.data contains: it might be a string of text
    // without HTML, so don't rely on jQuery correctly iterpreting
    // $(response.data) as new HTML rather than a CSS selector. Also, if
    // response.data contains top-level text nodes, they get lost with either
    // $(response.data) or $('<div></div>').replaceWith(response.data).
    var new_content_wrapped = $('<div></div>').html(response.data);
    var new_content = new_content_wrapped.contents();

    // For legacy reasons, the effects processing code assumes that new_content
    // consists of a single top-level element. Also, it has not been
    // sufficiently tested whether attachBehaviors() can be successfully called
    // with a context object that includes top-level text nodes. However, to
    // give developers full control of the HTML appearing in the page, and to
    // enable Ajax content to be inserted in places where DIV elements are not
    // allowed (e.g., within TABLE, TR, and SPAN parents), we check if the new
    // content satisfies the requirement of a single top-level element, and
    // only use the container DIV created above when it doesn't. For more
    // information, please see http://drupal.org/node/736066.
    if (new_content.length != 1 || new_content.get(0).nodeType != 1) {
      new_content = new_content_wrapped;
    }

    // If removing content from the wrapper, detach behaviors first.
    switch (method) {
      case 'html':
      case 'replaceWith':
      case 'replaceAll':
      case 'empty':
      case 'remove':
        var settings = response.settings || ajax.settings || Drupal.settings;
        Drupal.detachBehaviors(wrapper, settings);
    }

    // Add the new content to the page.
    wrapper[method](new_content);

    // Immediately hide the new content if we're using any effects.
    if (effect.showEffect != 'show') {
      new_content.hide();
    }

    // Determine which effect to use and what content will receive the
    // effect, then show the new content.
    if ($('.ajax-new-content', new_content).length > 0) {
      $('.ajax-new-content', new_content).hide();
      new_content.show();
      $('.ajax-new-content', new_content)[effect.showEffect](effect.showSpeed);
    }
    else if (effect.showEffect != 'show') {
      new_content[effect.showEffect](effect.showSpeed);
    }

    // Attach all JavaScript behaviors to the new content, if it was successfully
    // added to the page, this if statement allows #ajax['wrapper'] to be
    // optional.
    if (new_content.parents('html').length > 0) {
      // Apply any settings from the returned JSON if available.
      var settings = response.settings || ajax.settings || Drupal.settings;
      Drupal.attachBehaviors(new_content, settings);
    }
  },

  /**
   * Command to remove a chunk from the page.
   */
  remove: function (ajax, response, status) {
    var settings = response.settings || ajax.settings || Drupal.settings;
    Drupal.detachBehaviors($(response.selector), settings);
    $(response.selector).remove();
  },

  /**
   * Command to mark a chunk changed.
   */
  changed: function (ajax, response, status) {
    if (!$(response.selector).hasClass('ajax-changed')) {
      $(response.selector).addClass('ajax-changed');
      if (response.asterisk) {
        $(response.selector).find(response.asterisk).append(' <span class="ajax-changed">*</span> ');
      }
    }
  },

  /**
   * Command to provide an alert.
   */
  alert: function (ajax, response, status) {
    alert(response.text, response.title);
  },

  /**
   * Command to provide the jQuery css() function.
   */
  css: function (ajax, response, status) {
    $(response.selector).css(response.argument);
  },

  /**
   * Command to set the settings that will be used for other commands in this response.
   */
  settings: function (ajax, response, status) {
    if (response.merge) {
      $.extend(true, Drupal.settings, response.settings);
    }
    else {
      ajax.settings = response.settings;
    }
  },

  /**
   * Command to attach data using jQuery's data API.
   */
  data: function (ajax, response, status) {
    $(response.selector).data(response.name, response.value);
  },

  /**
   * Command to apply a jQuery method.
   */
  invoke: function (ajax, response, status) {
    var $element = $(response.selector);
    $element[response.method].apply($element, response.arguments);
  },

  /**
   * Command to restripe a table.
   */
  restripe: function (ajax, response, status) {
    // :even and :odd are reversed because jQuery counts from 0 and
    // we count from 1, so we're out of sync.
    // Match immediate children of the parent element to allow nesting.
    $('> tbody > tr:visible, > tr:visible', $(response.selector))
      .removeClass('odd even')
      .filter(':even').addClass('odd').end()
      .filter(':odd').addClass('even');
  }
};

})(jQuery);

This disables every input-form-submit-button while an ajax process is loading on the !!WHOLE SITE!!. Be carefull, i'm just in testing mode since today! I just added 3 lines of code to the original ajax.js file. They are marked es // COREHACK HERE!!

cheers!

mibfire’s picture

WOW!!! you are fcking hot, fast:DDD THX! To refine this more maybe there should be some user alert too, when user tryes to hit the submit button he/she wont understand what the hell is happening, so there should be a text next to each submit button that says "Please wait!".

I think this improvement should be built into the core.

mibfire’s picture

I did some investigation in this and we can avoid to rewrite the core ajax.js.

http://stackoverflow.com/questions/8543603/how-to-prevent-form-submit-du...

We should just use ajaxStart and ajaxSuccess methods to disable and enalbe the submit buttons.

mibfire’s picture

Here is a solution for this as i mentioned above:

			// Prevent form submit to be clickable when an ajax request is running
			$('body', context).once('ajax-form-submit-prevent', function() {
				var that = $(this);
				var ajaxProgressClass = 'ajax-progress';
				var warningText = $('<div class="' + ajaxProgressClass + ' ajax-progress-throbber"><div class="throbber">&nbsp;</div><div class="message">' + Drupal.t('Please wait...') + '</div></div>');
				// Select all submit buttons for disabling and enabling
				var submits = that.find('form input[type=submit]:visible');
				// If there are more than one submit button next to each other(for example cancel button) we add the ajax progress indicator to the first one to avoid multiple ones(requires juqery 1.9)
				var submitsFirst = that.find('form input[type=submit]:visible:first-of-type');
				$(document).ajaxStart(function() {
					if (!submits.next('.' + ajaxProgressClass).length) {
						submits.attr('disabled', 'disabled');
						submitsFirst.after(warningText);
					}
				}).ajaxStop(function() {
					submits.removeAttr('disabled');
					that.find('.' + ajaxProgressClass).remove();
				});
			});
dave bruns’s picture

I've been following this issue and testing a one-page checkout configuration where shipping may change depending on the shipping country.

However, I'm not able to get shipping to recalculate in a way that updates the shipping charges displayed in the cart view.

Can anyone give me a brief summary of what is currently possible with shipping 7.x-2.1, and any pointers on how to configure so that shipping can be recalculated dynamically based on the shipping address, if that is indeed possible?

andyg5000’s picture

Re-rolling the patch from @jessepinho since commerce_shipping.js is now in /js folder. His patch fixed my issue with having select2 drop down lists that generated a div.required selector. Obviously there's no val() there since it's a div. :input++

Lukas von Blarer’s picture

The patch works perfectly for me. Thanks!

mglaman’s picture

Status: Needs review » Reviewed & tested by the community

As in #56 and myself, patch from #55 works and supports proper calculation when shipping profile filled out.

mglaman’s picture

Rob C’s picture

Status: Reviewed & tested by the community » Needs work

And what about when the address changes? (country for example, that might change the calculation in the rule a bit).

andyg5000’s picture

The issue mentioned several times above where a user can bypass shipping recalculation by not "blurring" on the last required profile form element is still outstanding. This can lead to inaccurate shipping services being charged to the customer (ie: domestic shipping charge when it should be international).

To reproduce: Fill out all required form fields, but do not exit the last field (usually zipcode). Scroll to the bottom and click continue.

Here's a solution where we disable the continue button on the keydown event within the context of the customer profile fields. Once they blur the field or a change is made, the continue button is re-enabled. The patch includes the changes in #55 as well.

andyg5000’s picture

Status: Needs work » Needs review
andyg5000’s picture

bgilhome’s picture

Does it make sense to have access to $form_state then in commerce_shipping_service_rate_options / hook_commerce_shipping_service_rate_options_alter so that eg. the current country selection can be accessed? I've added the $form_state parameter to the relevant functions. Updated patch (from #62) attached.

joelpittet’s picture

@bgilhome could you provide an interdiff @see https://www.drupal.org/documentation/git/interdiff

So we can review you changes in comparison to #62?

xurizaemon’s picture

joelpittet’s picture

@xurizaemon I think you just may have accidentally reposted #62, thanks for giving the interdiff a shot.

xurizaemon’s picture

Good catch Joel, hopefully this is the real deal. Basically, "also pass $form_state (byref)" in commerce_shipping_service_rate_options() and hook_commerce_shipping_service_rate_options_alter().

Patch in comment 63 didn't apply cleanly to 7.x-2.x for me, didn't like the changes in .js

joelpittet’s picture

Thanks for giving that a try @xurizaemon. It doesn't look like it's being passed by reference, although I'm failing to see how that extra bit of code is helping the issue summary.

Also there is a hunk that got lost between #62 and #63 that isn't evident in the interdiff.

+++ b/js/commerce_shipping.js
@@ -36,7 +52,7 @@ $.fn.commerceCheckShippingRecalculation = function() {
-    $('[id^="edit-customer-profile-shipping"] .form-item').children('.required').each(function() {
+    $('[id^="edit-customer-profile-shipping"] :input[required], [id^="edit-customer-profile-shipping"] :input.required').each(function() {

This hunk got lost in #63 @andyg5000 is that bit needed?

andyg5000’s picture

I'm not sure why the alter hooks are being updated in this issue either. That should be a separate feature request. As, for the missing hunk, I think that was a copy/paste fail on my behalf and should be the following instead:

$('[id^="edit-customer-profile-billing"] :input[required], [id^="edit-customer-profile-shipping"] :input.required').each(function() {

Overall, the approach in my patch needs to be reviewed for usability. It's worked for me on a few projects to prevent the issue, but since this is a universal update, we'd want to make sure the verbiage and process is intuitive.

bgilhome’s picture

I've moved the alter hook changes to https://www.drupal.org/node/2452835 - "Access $form_state in shipping options hook ".

office@w3k.at’s picture

Just to be make sure:
#67 - add $form_state -> moved to https://www.drupal.org/node/2452835
#65 - changes to commerce_shipping.js,
already incorporated in commerce shipping 7.x-2.1
#63, #62 - obsolet versions of #65
#55 - some changes to jquery selectors in commerce_shipping.js i think obsolet

All these patches have already been incorporated in commerce shipping 7.x-2.
Ajax shipping recalculation works with all customer-profile fields - his issue can be closed.
Or am i missing something?

andyg5000’s picture

Re-rolling #62 against HEAD.

Patch provides:
Ability to use .required class or input[required] HTML5 selectors (#45)
Disables continue/submit button while typing into required fields (Addresses #7 bullet 4, #60)

I've also included a screenshot so you can see what it looks like when you're typing in a required field, but have not yet "blurred"

joelpittet’s picture

Status: Needs review » Needs work

Just a quick review but looks like a decent change @andyg5000.

  1. +++ b/js/commerce_shipping.js
    @@ -5,14 +5,38 @@
    -  }
    +  },
    

    This trailing comma causes problems with IE.

  2. +++ b/js/commerce_shipping.js
    @@ -5,14 +5,38 @@
    +  $('.checkout-continue', context).attr({'disabled' : 'disabled'})
    +  .val(Drupal.t('Please click recalculate shipping to proceed'));
    ...
    +  $('.checkout-continue', context).attr({'disabled' : false})
    +  .val(continueText);
    

    May want to indent or keep them on the same line so they don't look like statements.

mstrelan’s picture

Sorry I haven't really read this thoroughly, but I need to refresh the shipping services when the country is changed, regardless of whether or not the full address validates. Or at least hide all the shipping services until the address validates. Can't have domestic shipping visible when the shipping address has an international country selected. Is this in scope of this issue?

andyg5000’s picture

Thanks for the feedback @joelpittet

@mstrelan, I think that may be better suited for another issue as a feature request with a reference to this thread.

torgosPizza’s picture

Minor typo:

+ * Disables checkout to prevent to make sure recalculate happens.

  • rszrama committed 708a8e3 on 8.x-2.x
    Issue #1287124 by rszrama, andyg5000, googletorp: support automatically...
  • rszrama committed bc0109d on 8.x-2.x authored by cvangysel
    Issue #1287124 follow-up by cvangysel, rszrama: update AJAX shipping...
siva_drupal’s picture

The given patch #75 does not update my shipping details in ajax callback. I have flat rating shipping in my website will be working based on country. After applying this patch also I didn't get any changes on my shipping services. Please share any idea to solve this.

arunkumark’s picture

+1
As per the comment #78, there is no shipping information update happens after applying patch #75.

This issue related Update the cart contents pane's order totals when shipping is selected

Please share any idea to solve this.

@siva_drupal, Please try this patch may helps.
https://www.drupal.org/node/1287126#comment-11942408

andyg5000’s picture

Related #2830800: [Follow-up] Avoid overwriting an already updated order : fixes ajax recalculation of shipping by making sure field changes to profiles trigger an order save.

rokurtz’s picture

patch.patch:159: trailing whitespace.

patch.patch:173: trailing whitespace.

patch.patch:185: trailing whitespace.

patch.patch:209: trailing whitespace.

patch.patch:223: trailing whitespace.

Checking patch commerce_shipping.module...
Checking patch commerce_shipping.rules_defaults.inc...
Checking patch includes/commerce_shipping.checkout_pane.inc...
Checking patch js/commerce_shipping.js...
Applied patch commerce_shipping.module cleanly.
Applied patch commerce_shipping.rules_defaults.inc cleanly.
Applied patch includes/commerce_shipping.checkout_pane.inc cleanly.
Applied patch js/commerce_shipping.js cleanly.
warning: squelched 1 whitespace error
warning: 6 lines add whitespace errors.