From 79167dc4de76a42b1f85c2bbaa9859ecfbf8a56a Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Fri, 16 Dec 2011 13:30:17 -0500 Subject: [PATCH 01/17] Issue #1373236 - Provide generic ajax manipulation for checkout page. --- shipping/uc_quote/uc_quote.module | 88 +++++++++--------------------------- uc_cart/uc_cart.api.php | 40 +++++++++++++++++ uc_cart/uc_cart.pages.inc | 82 ++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 66 deletions(-) diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index 5a5013e..4778e1c 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -240,73 +240,29 @@ function uc_quote_form_alter(&$form, &$form_state, $form_id) { } } -/** - * Implements hook_form_FORM_ID_alter() for uc_cart_checkout_form(). - * - * Adds Ajax shipping quote functionality to the checkout form. - */ -function uc_quote_form_uc_cart_checkout_form_alter(&$form, &$form_state) { - if (isset($form['panes']['delivery']['address'])) { - $form['panes']['delivery']['address']['#process'] = array('uc_store_process_address_field', 'uc_quote_process_checkout_address'); - } - if (isset($form['panes']['delivery']['select_address'])) { - $form['panes']['delivery']['select_address']['#ajax']['callback'] = 'uc_quote_select_address'; - } - // @todo: Figure out what needs to be done when copy-address box is checked. - - if (isset($form['panes']['payment']['line_items'])) { - $form['panes']['quotes']['quotes']['quote_option']['#ajax'] = array( - 'callback' => 'uc_payment_get_totals', - 'wrapper' => 'line-items-div', - ); - } -} - -/** - * Ajax callback: Updates quotes when a saved address is selected. - */ -function uc_quote_select_address($form, $form_state) { - $return = uc_quote_checkout_returned_rates($form, $form_state); - $address = uc_checkout_pane_address_render($form, $form_state); - - $return['#commands'][] = ajax_command_replace('#' . $form_state['triggering_element']['#ajax']['wrapper'], drupal_render($address)); - - return $return; -} - -/** - * Ajax callback: Updates shipping quotes when the delivery country changes. - */ -function uc_quote_country_change_wrapper(&$form, &$form_state) { - $return = uc_quote_checkout_returned_rates($form, $form_state); - $return['#commands'][] = ajax_command_replace('#uc-store-address-delivery-zone-wrapper', drupal_render(uc_store_update_address_field_zones($form, $form_state))); - return $return; -} - -/** - * Process callback: Adds Ajax functionality to delivery address fields. - */ -function uc_quote_process_checkout_address($element, $form_state) { - $ajax = array( - 'callback' => 'uc_quote_checkout_returned_rates', - 'effect' => 'slide', - 'progress' => array( - 'type' => 'throbber', - 'message' => t('Receiving shipping quotes...'), - ), - ); - - if (isset($element['delivery_postal_code'])) { - $element['delivery_postal_code']['#ajax'] = $ajax; - } - // The following replaces "uc_store_process_address_field" from uc_store - // with a wrapper that will update the available quotes when the country - // is changed. - if (isset($element['delivery_country'])) { - $element['delivery_country']['#ajax']['callback'] = 'uc_quote_country_change_wrapper'; +function uc_quote_uc_cart_checkout_ajax($child, $parent, $form_state) { + switch ($child) { + case 'delivery_country': + case 'delivery_postal_code': + case 'select_address': + return array( + 'callback' => 'uc_quote_checkout_returned_rates', + 'effect' => 'slide', + 'progress' => array( + 'type' => 'throbber', + 'message' => t('Receiving shipping quotes...'), + ), + ); + break; + case 'quote_option': + if (isset($form_state['complete form']['panes']['payment']['line_items'])) { + return array( + 'callback' => 'uc_payment_get_totals', + 'wrapper' => 'line-items-div', + ); + } + break; } - - return $element; } /** diff --git a/uc_cart/uc_cart.api.php b/uc_cart/uc_cart.api.php index ee5a374..4433152 100644 --- a/uc_cart/uc_cart.api.php +++ b/uc_cart/uc_cart.api.php @@ -422,5 +422,45 @@ function hook_uc_update_cart_item($nid, $data = array(), $qty, $cid = NULL) { } /** + * Allows modules to add ajax callbacks to checkout form elements in an orderly manner. The callbacks + * are invoked in module weight order to generate a list of ajax commands which are then returned to + * the page. Note that this hook is invoked after the $parent element has been processed - so, for example, + * a 'radios' element will already have expanded to include its radio-button children. The children, + * however, will not yet have been processed. + * + * Ajax options (such as the effect, progress message, etc.) will be determined by the heaviest module + * implementing this hook. However, the *lightest* callback to invoke theme('status_messages', ...) will + * prevent their display by heavier modules. + * + * @param $child + * The key of the element to be modified. + * @param $parent + * The element which is the parent of $child. Can be used to provide additional context. + * @param $form_state + * The current $form_state array. + * + * @return + * An array as would be provided for the '#ajax' attribute of an element. Currently, you must specify a + * 'callback' key ('path' is not supported). If the callback returns a string or renderable array, you + * must also specify a wrapper. + */ +function hook_uc_cart_checkout_ajax($child, $parent, $form_state) { + if ($child == 'delivery_country') { + return array( + 'callback' => 'uc_cart_checkout_ajax_test', + 'wrapper' => 'cart-pane', + 'effect' => 'slide', + 'progress' => array( + 'type' => 'throbber', + 'message' => t('Modifying cart contents...'), + ), + ); + } + return FALSE; +} + + + +/** * @} End of "addtogroup hooks". */ diff --git a/uc_cart/uc_cart.pages.inc b/uc_cart/uc_cart.pages.inc index 48c18cb..47ac785 100644 --- a/uc_cart/uc_cart.pages.inc +++ b/uc_cart/uc_cart.pages.inc @@ -152,6 +152,83 @@ function uc_cart_checkout() { return drupal_get_form('uc_cart_checkout_form', $order); } + +/** + * Generic ajax callback for the checkout form. + * Process a list of ajax commands attached to the specified triggering element via hook_uc_cart_checkout_ajax(). + */ +function uc_cart_checkout_ajax($form, $form_state) { + $element = $form_state['triggering_element']; + if (!empty($element['#ajax']['list'])) { + $commands = array(); + foreach ($element['#ajax']['list'] as $ajax) { + if (!empty($ajax['callback']) && function_exists($ajax['callback'])) { + $result = call_user_func($ajax['callback'], $form, $form_state); + if (!empty($result)) { + if (is_array($result) && !empty($result['#type']) && $result['#type'] == 'ajax') { + // If the callback returned an array of commands, simply add these to the list. + $commands = array_merge($commands, $result['#commands']); + } + elseif (!empty($ajax['wrapper'])) { + // Otherwise, assume the callback returned a string or render-array, and insert it into the wrapper. + $html = is_string($result) ? $result : drupal_render($result); + $commands[] = ajax_command_replace('#' . $ajax['wrapper'], $html); + $commands[] = ajax_command_prepend('#' . $ajax['wrapper'], theme('status_messages')); + } + } + } + } + } + if (!empty($commands)) { + return array('#type' => 'ajax', '#commands' => $commands); + } +} + +/** + * Process callback to add ajax to form elements. + */ +function uc_cart_checkout_form_process($form, $form_state) { + // We have to operate on the children rather than on the element itself, because the #process + // functions are called *after* form_handle_input_elements(), which is where the triggering + // element is determined. If we haven't added an '#ajax' key by that time, drupal won't be + // able to determine which callback to invoke. + foreach (element_children($form) as $child) { + $element =& $form[$child]; + + // First add this process function recursively to the children. + if (empty($element['#process']) && !empty($element['#type'])) { + // We want to be sure the default process functions for the element type are called. + $info = element_info($element['#type']); + if (!empty($info['#process'])) { + $element['#process'] = $info['#process']; + } + } + $element['#process'][] = 'uc_cart_checkout_form_process'; + + // Build a list of the ajax that modules want to add to this element. + $list = array(); + foreach (module_implements('uc_cart_checkout_ajax') as $module) { + $list[] = module_invoke($module, 'uc_cart_checkout_ajax', $child, $form, $form_state); + } + $list = array_filter($list); + if (!empty($list)) { + if (!empty($element['#ajax'])) { + array_unshift($list, $element['#ajax']); + } + $element['#ajax'] = array(); + foreach ($list as $ajax) { + $element['#ajax'] = $ajax + $element['#ajax']; + } + $element['#ajax'] = array( + 'callback' => 'uc_cart_checkout_ajax', + 'list' => $list + ) + $element['#ajax']; + } + } + + return $form; +} + /** * The checkout form built up from the enabled checkout panes. * @@ -278,9 +355,14 @@ function uc_cart_checkout_form($form, &$form_state, $order) { '#type' => 'submit', '#value' => t('Review order'), ); + $form['#process'] = array('uc_cart_checkout_form_process'); +<<<<<<< HEAD unset($_SESSION['uc_checkout'][$order->order_id]); +======= + unset($_SESSION['do_review']); +>>>>>>> Issue #1373236 - Provide generic ajax manipulation for checkout page. return $form; } -- 1.7.3.4 From 890dfce10270b90b60cede0858cef0dda2cd4f28 Mon Sep 17 00:00:00 2001 From: Dave Long Date: Sat, 17 Dec 2011 17:44:42 +0000 Subject: [PATCH 02/17] Reorganise code and add/reformat comments. --- shipping/uc_quote/uc_quote.module | 3 + uc_cart/uc_cart.pages.inc | 166 +++++++++++++++++++------------------ 2 files changed, 87 insertions(+), 82 deletions(-) diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index 4778e1c..f4efce4 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -240,6 +240,9 @@ function uc_quote_form_alter(&$form, &$form_state, $form_id) { } } +/** + * Implements hook_uc_cart_checkout_ajax(). + */ function uc_quote_uc_cart_checkout_ajax($child, $parent, $form_state) { switch ($child) { case 'delivery_country': diff --git a/uc_cart/uc_cart.pages.inc b/uc_cart/uc_cart.pages.inc index 47ac785..38dc0f8 100644 --- a/uc_cart/uc_cart.pages.inc +++ b/uc_cart/uc_cart.pages.inc @@ -152,91 +152,15 @@ function uc_cart_checkout() { return drupal_get_form('uc_cart_checkout_form', $order); } - -/** - * Generic ajax callback for the checkout form. - * Process a list of ajax commands attached to the specified triggering element via hook_uc_cart_checkout_ajax(). - */ -function uc_cart_checkout_ajax($form, $form_state) { - $element = $form_state['triggering_element']; - if (!empty($element['#ajax']['list'])) { - $commands = array(); - foreach ($element['#ajax']['list'] as $ajax) { - if (!empty($ajax['callback']) && function_exists($ajax['callback'])) { - $result = call_user_func($ajax['callback'], $form, $form_state); - if (!empty($result)) { - if (is_array($result) && !empty($result['#type']) && $result['#type'] == 'ajax') { - // If the callback returned an array of commands, simply add these to the list. - $commands = array_merge($commands, $result['#commands']); - } - elseif (!empty($ajax['wrapper'])) { - // Otherwise, assume the callback returned a string or render-array, and insert it into the wrapper. - $html = is_string($result) ? $result : drupal_render($result); - $commands[] = ajax_command_replace('#' . $ajax['wrapper'], $html); - $commands[] = ajax_command_prepend('#' . $ajax['wrapper'], theme('status_messages')); - } - } - } - } - } - if (!empty($commands)) { - return array('#type' => 'ajax', '#commands' => $commands); - } -} - -/** - * Process callback to add ajax to form elements. - */ -function uc_cart_checkout_form_process($form, $form_state) { - // We have to operate on the children rather than on the element itself, because the #process - // functions are called *after* form_handle_input_elements(), which is where the triggering - // element is determined. If we haven't added an '#ajax' key by that time, drupal won't be - // able to determine which callback to invoke. - foreach (element_children($form) as $child) { - $element =& $form[$child]; - - // First add this process function recursively to the children. - if (empty($element['#process']) && !empty($element['#type'])) { - // We want to be sure the default process functions for the element type are called. - $info = element_info($element['#type']); - if (!empty($info['#process'])) { - $element['#process'] = $info['#process']; - } - } - $element['#process'][] = 'uc_cart_checkout_form_process'; - - // Build a list of the ajax that modules want to add to this element. - $list = array(); - foreach (module_implements('uc_cart_checkout_ajax') as $module) { - $list[] = module_invoke($module, 'uc_cart_checkout_ajax', $child, $form, $form_state); - } - $list = array_filter($list); - if (!empty($list)) { - if (!empty($element['#ajax'])) { - array_unshift($list, $element['#ajax']); - } - $element['#ajax'] = array(); - foreach ($list as $ajax) { - $element['#ajax'] = $ajax + $element['#ajax']; - } - $element['#ajax'] = array( - 'callback' => 'uc_cart_checkout_ajax', - 'list' => $list - ) + $element['#ajax']; - } - } - - return $form; -} - /** * The checkout form built up from the enabled checkout panes. * * @param $order * The order that is being checked out. * + * @see uc_cart_checkout_form_process() * @see uc_cart_checkout_form_validate() - * @see uc_cart_checkout_form_review() + * @see uc_cart_checkout_form_submit() * @see uc_cart_checkout_review() * @see theme_uc_cart_checkout_form() * @ingroup forms @@ -357,12 +281,8 @@ function uc_cart_checkout_form($form, &$form_state, $order) { ); $form['#process'] = array('uc_cart_checkout_form_process'); -<<<<<<< HEAD unset($_SESSION['uc_checkout'][$order->order_id]); -======= - unset($_SESSION['do_review']); ->>>>>>> Issue #1373236 - Provide generic ajax manipulation for checkout page. return $form; } @@ -447,6 +367,88 @@ function uc_cart_checkout_form_cancel($form, &$form_state) { } /** + * Form process callback to add Ajax to form elements. + * + * @see uc_cart_checkout_form() + * @see uc_cart_checkout_ajax() + */ +function uc_cart_checkout_form_process($form, $form_state) { + // We have to operate on the children rather than on the element itself, as + // #process functions are called *after* form_handle_input_elements(), + // which is where the triggering element is determined. If we haven't added + // an '#ajax' key by that time, Drupal won't be able to determine which + // callback to invoke. + foreach (element_children($form) as $child) { + $element =& $form[$child]; + + // First add this process function recursively to the children. + if (empty($element['#process']) && !empty($element['#type'])) { + // We want to be sure the default process functions for the element type are called. + $info = element_info($element['#type']); + if (!empty($info['#process'])) { + $element['#process'] = $info['#process']; + } + } + $element['#process'][] = 'uc_cart_checkout_form_process'; + + // Build a list of the ajax that modules want to add to this element. + $list = array(); + foreach (module_implements('uc_cart_checkout_ajax') as $module) { + $list[] = module_invoke($module, 'uc_cart_checkout_ajax', $child, $form, $form_state); + } + $list = array_filter($list); + if (!empty($list)) { + if (!empty($element['#ajax'])) { + array_unshift($list, $element['#ajax']); + } + $element['#ajax'] = array(); + foreach ($list as $ajax) { + $element['#ajax'] = $ajax + $element['#ajax']; + } + $element['#ajax'] = array( + 'callback' => 'uc_cart_checkout_ajax', + 'list' => $list + ) + $element['#ajax']; + } + } + + return $form; +} + +/** + * Ajax callback multiplexer for the checkout form. + * + * Processes a list of Ajax commands attached to the triggering element + * via hook_uc_cart_checkout_ajax(). + */ +function uc_cart_checkout_ajax($form, $form_state) { + $element = $form_state['triggering_element']; + if (!empty($element['#ajax']['list'])) { + $commands = array(); + foreach ($element['#ajax']['list'] as $ajax) { + if (!empty($ajax['callback']) && function_exists($ajax['callback'])) { + $result = call_user_func($ajax['callback'], $form, $form_state); + if (!empty($result)) { + if (is_array($result) && !empty($result['#type']) && $result['#type'] == 'ajax') { + // If the callback returned an array of commands, simply add these to the list. + $commands = array_merge($commands, $result['#commands']); + } + elseif (!empty($ajax['wrapper'])) { + // Otherwise, assume the callback returned a string or render-array, and insert it into the wrapper. + $html = is_string($result) ? $result : drupal_render($result); + $commands[] = ajax_command_replace('#' . $ajax['wrapper'], $html); + $commands[] = ajax_command_prepend('#' . $ajax['wrapper'], theme('status_messages')); + } + } + } + } + } + if (!empty($commands)) { + return array('#type' => 'ajax', '#commands' => $commands); + } +} + +/** * Allows a customer to review their order before finally submitting it. * * @see uc_cart_checkout_form() -- 1.7.3.4 From 742090cea8ef2df6c3defd840d7b3b95f15adb6e Mon Sep 17 00:00:00 2001 From: Dave Long Date: Sat, 17 Dec 2011 23:19:42 +0000 Subject: [PATCH 03/17] Issue #1373236: Simplify hook_uc_cart_checkout_ajax() and ensure it is only called once. --- payment/uc_payment/uc_payment.module | 4 ++- shipping/uc_quote/uc_quote.module | 33 +++++++------------- uc_cart/uc_cart.pages.inc | 55 ++++++++++++++++----------------- 3 files changed, 41 insertions(+), 51 deletions(-) diff --git a/payment/uc_payment/uc_payment.module b/payment/uc_payment/uc_payment.module index 4e7df73..848cc60 100644 --- a/payment/uc_payment/uc_payment.module +++ b/payment/uc_payment/uc_payment.module @@ -252,7 +252,9 @@ function uc_payment_uc_order_state() { * The formatted HTML of the order total preview if $return is set to TRUE. */ function uc_payment_get_totals($form, $form_state) { - return $form['panes']['payment']['line_items']; + $commands[] = ajax_command_replace('#line-items-div', drupal_render($form['panes']['payment']['line_items'])); + + return array('#type' => 'ajax', '#commands' => $commands); } /** diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index f4efce4..9005176 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -243,29 +243,18 @@ function uc_quote_form_alter(&$form, &$form_state, $form_id) { /** * Implements hook_uc_cart_checkout_ajax(). */ -function uc_quote_uc_cart_checkout_ajax($child, $parent, $form_state) { - switch ($child) { - case 'delivery_country': - case 'delivery_postal_code': - case 'select_address': - return array( - 'callback' => 'uc_quote_checkout_returned_rates', - 'effect' => 'slide', - 'progress' => array( - 'type' => 'throbber', - 'message' => t('Receiving shipping quotes...'), - ), - ); - break; - case 'quote_option': - if (isset($form_state['complete form']['panes']['payment']['line_items'])) { - return array( - 'callback' => 'uc_payment_get_totals', - 'wrapper' => 'line-items-div', - ); - } - break; +function uc_quote_uc_cart_checkout_ajax($form, $form_state) { + $fields = array( + 'delivery_country' => 'uc_quote_checkout_returned_rates', + 'delivery_postal_code' => 'uc_quote_checkout_returned_rates', + 'select_address' => 'uc_quote_checkout_returned_rates', + ); + + if (isset($form['panes']['payment']['line_items'])) { + $fields['quote_option'] = 'uc_payment_get_totals'; } + + return $fields; } /** diff --git a/uc_cart/uc_cart.pages.inc b/uc_cart/uc_cart.pages.inc index 38dc0f8..ca89f67 100644 --- a/uc_cart/uc_cart.pages.inc +++ b/uc_cart/uc_cart.pages.inc @@ -373,6 +373,12 @@ function uc_cart_checkout_form_cancel($form, &$form_state) { * @see uc_cart_checkout_ajax() */ function uc_cart_checkout_form_process($form, $form_state) { + static $fields; + + if (!isset($fields)) { + $fields = module_invoke_all('uc_cart_checkout_ajax', $form, $form_state); + } + // We have to operate on the children rather than on the element itself, as // #process functions are called *after* form_handle_input_elements(), // which is where the triggering element is determined. If we haven't added @@ -391,24 +397,20 @@ function uc_cart_checkout_form_process($form, $form_state) { } $element['#process'][] = 'uc_cart_checkout_form_process'; - // Build a list of the ajax that modules want to add to this element. - $list = array(); - foreach (module_implements('uc_cart_checkout_ajax') as $module) { - $list[] = module_invoke($module, 'uc_cart_checkout_ajax', $child, $form, $form_state); - } - $list = array_filter($list); - if (!empty($list)) { + if (isset($fields[$child])) { + $list = is_array($fields[$child]) ? $fields[$child] : array($fields[$child]); + if (!empty($element['#ajax'])) { - array_unshift($list, $element['#ajax']); + array_unshift($list, $element['#ajax']['callback']); } - $element['#ajax'] = array(); - foreach ($list as $ajax) { - $element['#ajax'] = $ajax + $element['#ajax']; + else { + $element['#ajax'] = array(); } - $element['#ajax'] = array( + + $element['#ajax'] = array_merge($element['#ajax'], array( 'callback' => 'uc_cart_checkout_ajax', - 'list' => $list - ) + $element['#ajax']; + 'list' => $list, + )); } } @@ -425,20 +427,17 @@ function uc_cart_checkout_ajax($form, $form_state) { $element = $form_state['triggering_element']; if (!empty($element['#ajax']['list'])) { $commands = array(); - foreach ($element['#ajax']['list'] as $ajax) { - if (!empty($ajax['callback']) && function_exists($ajax['callback'])) { - $result = call_user_func($ajax['callback'], $form, $form_state); - if (!empty($result)) { - if (is_array($result) && !empty($result['#type']) && $result['#type'] == 'ajax') { - // If the callback returned an array of commands, simply add these to the list. - $commands = array_merge($commands, $result['#commands']); - } - elseif (!empty($ajax['wrapper'])) { - // Otherwise, assume the callback returned a string or render-array, and insert it into the wrapper. - $html = is_string($result) ? $result : drupal_render($result); - $commands[] = ajax_command_replace('#' . $ajax['wrapper'], $html); - $commands[] = ajax_command_prepend('#' . $ajax['wrapper'], theme('status_messages')); - } + foreach ($element['#ajax']['list'] as $callback) { + if (!empty($callback) && function_exists($callback) && $result = $callback($form, $form_state)) { + if (is_array($result) && !empty($result['#type']) && $result['#type'] == 'ajax') { + // If the callback returned an array of commands, simply add these to the list. + $commands = array_merge($commands, $result['#commands']); + } + elseif (!empty($element['#ajax']['wrapper'])) { + // Otherwise, assume the callback returned a string or render-array, and insert it into the wrapper. + $html = is_string($result) ? $result : drupal_render($result); + $commands[] = ajax_command_replace('#' . $element['#ajax']['wrapper'], $html); + $commands[] = ajax_command_prepend('#' . $element['#ajax']['wrapper'], theme('status_messages')); } } } -- 1.7.3.4 From 91ee373eb514cd8e7cd80019d4123ede0a17adc7 Mon Sep 17 00:00:00 2001 From: Dave Long Date: Sat, 17 Dec 2011 19:07:52 +0000 Subject: [PATCH 04/17] Issue #1373236: Allow country selection to limit and update payment methods at checkout. --- payment/uc_payment/uc_payment_checkout_pane.inc | 40 ++++++++++++++++------ 1 files changed, 29 insertions(+), 11 deletions(-) diff --git a/payment/uc_payment/uc_payment_checkout_pane.inc b/payment/uc_payment/uc_payment_checkout_pane.inc index 755203c..95a6592 100644 --- a/payment/uc_payment/uc_payment_checkout_pane.inc +++ b/payment/uc_payment/uc_payment_checkout_pane.inc @@ -25,8 +25,6 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL $methods = _uc_payment_method_list(); $options = array(); - $default = NULL; - foreach ($methods as $id => $method) { $set = rules_config_load('uc_payment_method_' . $method['id']); if ($set && !$set->execute($order)) { @@ -43,15 +41,12 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL drupal_goto('cart'); } - if (count($options)) { - if (isset($form_state['values']) && - isset($form_state['values']['panes']['payment']['payment_method']) && - in_array($form_state['values']['panes']['payment']['payment_method'], array_keys($options))) { - $default = $form_state['values']['panes']['payment']['payment_method']; - } - else { - $default = (count($options) == 1 || empty($order->payment_method)) ? key($options) : $order->payment_method; - } + $default = $order->payment_method; + if (isset($form_state['values']['panes']['payment']['payment_method'])) { + $default = $form_state['values']['panes']['payment']['payment_method']; + } + if (!isset($options[$default])) { + $default = key($options); } if (count($options) > 1) { @@ -76,6 +71,8 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL 'type' => 'throbber', ), ), + '#prefix' => '
', + '#suffix' => '
', ); $contents['details'] = array( @@ -143,3 +140,24 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL function uc_payment_checkout_payment_details($form, $form_state) { return $form['panes']['payment']['details']; } + +/** + * Implements hook_uc_cart_checkout_ajax(). + */ +function uc_payment_uc_cart_checkout_ajax($form, $form_state) { + return array( + 'billing_country' => 'uc_payment_checkout_payment_methods', + 'delivery_country' => 'uc_payment_checkout_payment_methods', + ); +} + +/** + * AJAX callback for updating the list of payment methods. + */ +function uc_payment_checkout_payment_methods($form, $form_state) { + $commands[] = ajax_command_replace('#payment-method', drupal_render($form['panes']['payment']['payment_method'])); + $commands[] = ajax_command_replace('#payment-details', drupal_render($form['panes']['payment']['details'])); + $commands[] = ajax_command_prepend('#payment-method', theme('status_messages')); + + return array('#type' => 'ajax', '#commands' => $commands); +} -- 1.7.3.4 From 6744abd17b0e0af1588bfdf5ca3dc180fbca99f6 Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Sun, 18 Dec 2011 21:37:20 -0500 Subject: [PATCH 05/17] Issue #1373236: Abstract ajax attach functionality so it is available on all forms. --- payment/uc_payment/uc_payment_checkout_pane.inc | 14 ++-- shipping/uc_quote/uc_quote.module | 24 ++++--- uc_cart/uc_cart.api.php | 40 ----------- uc_cart/uc_cart.pages.inc | 83 +---------------------- uc_store/includes/uc_ajax_attach.inc | 79 +++++++++++++++++++++ uc_store/uc_store.api.php | 21 ++++++ uc_store/uc_store.module | 1 + 7 files changed, 123 insertions(+), 139 deletions(-) create mode 100644 uc_store/includes/uc_ajax_attach.inc diff --git a/payment/uc_payment/uc_payment_checkout_pane.inc b/payment/uc_payment/uc_payment_checkout_pane.inc index 95a6592..b12775a 100644 --- a/payment/uc_payment/uc_payment_checkout_pane.inc +++ b/payment/uc_payment/uc_payment_checkout_pane.inc @@ -142,13 +142,15 @@ function uc_payment_checkout_payment_details($form, $form_state) { } /** - * Implements hook_uc_cart_checkout_ajax(). + * Implements hook_uc_ajax_attach(). */ -function uc_payment_uc_cart_checkout_ajax($form, $form_state) { - return array( - 'billing_country' => 'uc_payment_checkout_payment_methods', - 'delivery_country' => 'uc_payment_checkout_payment_methods', - ); +function uc_payment_uc_ajax_attach($form, $form_state) { + if ($form['#form_id'] == 'uc_cart_checkout_form') { + return array( + 'billing_country' => 'uc_payment_checkout_payment_methods', + 'delivery_country' => 'uc_payment_checkout_payment_methods', + ); + } } /** diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index 9005176..714c1ea 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -241,20 +241,22 @@ function uc_quote_form_alter(&$form, &$form_state, $form_id) { } /** - * Implements hook_uc_cart_checkout_ajax(). + * Implements hook_uc_ajax_attach(). */ -function uc_quote_uc_cart_checkout_ajax($form, $form_state) { - $fields = array( - 'delivery_country' => 'uc_quote_checkout_returned_rates', - 'delivery_postal_code' => 'uc_quote_checkout_returned_rates', - 'select_address' => 'uc_quote_checkout_returned_rates', - ); +function uc_quote_uc_ajax_attach($form, $form_state) { + if ($form['#form_id'] == 'uc_cart_checkout_form') { + $fields = array( + 'delivery_country' => 'uc_quote_checkout_returned_rates', + 'delivery_postal_code' => 'uc_quote_checkout_returned_rates', + 'select_address' => 'uc_quote_checkout_returned_rates', + ); - if (isset($form['panes']['payment']['line_items'])) { - $fields['quote_option'] = 'uc_payment_get_totals'; - } + if (isset($form['panes']['payment']['line_items'])) { + $fields['quote_option'] = 'uc_payment_get_totals'; + } - return $fields; + return $fields; + } } /** diff --git a/uc_cart/uc_cart.api.php b/uc_cart/uc_cart.api.php index 4433152..ee5a374 100644 --- a/uc_cart/uc_cart.api.php +++ b/uc_cart/uc_cart.api.php @@ -422,45 +422,5 @@ function hook_uc_update_cart_item($nid, $data = array(), $qty, $cid = NULL) { } /** - * Allows modules to add ajax callbacks to checkout form elements in an orderly manner. The callbacks - * are invoked in module weight order to generate a list of ajax commands which are then returned to - * the page. Note that this hook is invoked after the $parent element has been processed - so, for example, - * a 'radios' element will already have expanded to include its radio-button children. The children, - * however, will not yet have been processed. - * - * Ajax options (such as the effect, progress message, etc.) will be determined by the heaviest module - * implementing this hook. However, the *lightest* callback to invoke theme('status_messages', ...) will - * prevent their display by heavier modules. - * - * @param $child - * The key of the element to be modified. - * @param $parent - * The element which is the parent of $child. Can be used to provide additional context. - * @param $form_state - * The current $form_state array. - * - * @return - * An array as would be provided for the '#ajax' attribute of an element. Currently, you must specify a - * 'callback' key ('path' is not supported). If the callback returns a string or renderable array, you - * must also specify a wrapper. - */ -function hook_uc_cart_checkout_ajax($child, $parent, $form_state) { - if ($child == 'delivery_country') { - return array( - 'callback' => 'uc_cart_checkout_ajax_test', - 'wrapper' => 'cart-pane', - 'effect' => 'slide', - 'progress' => array( - 'type' => 'throbber', - 'message' => t('Modifying cart contents...'), - ), - ); - } - return FALSE; -} - - - -/** * @} End of "addtogroup hooks". */ diff --git a/uc_cart/uc_cart.pages.inc b/uc_cart/uc_cart.pages.inc index ca89f67..262696c 100644 --- a/uc_cart/uc_cart.pages.inc +++ b/uc_cart/uc_cart.pages.inc @@ -279,7 +279,7 @@ function uc_cart_checkout_form($form, &$form_state, $order) { '#type' => 'submit', '#value' => t('Review order'), ); - $form['#process'] = array('uc_cart_checkout_form_process'); + $form['#process'] = array('uc_ajax_attach_process_form'); unset($_SESSION['uc_checkout'][$order->order_id]); @@ -367,87 +367,6 @@ function uc_cart_checkout_form_cancel($form, &$form_state) { } /** - * Form process callback to add Ajax to form elements. - * - * @see uc_cart_checkout_form() - * @see uc_cart_checkout_ajax() - */ -function uc_cart_checkout_form_process($form, $form_state) { - static $fields; - - if (!isset($fields)) { - $fields = module_invoke_all('uc_cart_checkout_ajax', $form, $form_state); - } - - // We have to operate on the children rather than on the element itself, as - // #process functions are called *after* form_handle_input_elements(), - // which is where the triggering element is determined. If we haven't added - // an '#ajax' key by that time, Drupal won't be able to determine which - // callback to invoke. - foreach (element_children($form) as $child) { - $element =& $form[$child]; - - // First add this process function recursively to the children. - if (empty($element['#process']) && !empty($element['#type'])) { - // We want to be sure the default process functions for the element type are called. - $info = element_info($element['#type']); - if (!empty($info['#process'])) { - $element['#process'] = $info['#process']; - } - } - $element['#process'][] = 'uc_cart_checkout_form_process'; - - if (isset($fields[$child])) { - $list = is_array($fields[$child]) ? $fields[$child] : array($fields[$child]); - - if (!empty($element['#ajax'])) { - array_unshift($list, $element['#ajax']['callback']); - } - else { - $element['#ajax'] = array(); - } - - $element['#ajax'] = array_merge($element['#ajax'], array( - 'callback' => 'uc_cart_checkout_ajax', - 'list' => $list, - )); - } - } - - return $form; -} - -/** - * Ajax callback multiplexer for the checkout form. - * - * Processes a list of Ajax commands attached to the triggering element - * via hook_uc_cart_checkout_ajax(). - */ -function uc_cart_checkout_ajax($form, $form_state) { - $element = $form_state['triggering_element']; - if (!empty($element['#ajax']['list'])) { - $commands = array(); - foreach ($element['#ajax']['list'] as $callback) { - if (!empty($callback) && function_exists($callback) && $result = $callback($form, $form_state)) { - if (is_array($result) && !empty($result['#type']) && $result['#type'] == 'ajax') { - // If the callback returned an array of commands, simply add these to the list. - $commands = array_merge($commands, $result['#commands']); - } - elseif (!empty($element['#ajax']['wrapper'])) { - // Otherwise, assume the callback returned a string or render-array, and insert it into the wrapper. - $html = is_string($result) ? $result : drupal_render($result); - $commands[] = ajax_command_replace('#' . $element['#ajax']['wrapper'], $html); - $commands[] = ajax_command_prepend('#' . $element['#ajax']['wrapper'], theme('status_messages')); - } - } - } - } - if (!empty($commands)) { - return array('#type' => 'ajax', '#commands' => $commands); - } -} - -/** * Allows a customer to review their order before finally submitting it. * * @see uc_cart_checkout_form() diff --git a/uc_store/includes/uc_ajax_attach.inc b/uc_store/includes/uc_ajax_attach.inc new file mode 100644 index 0000000..8d98247 --- /dev/null +++ b/uc_store/includes/uc_ajax_attach.inc @@ -0,0 +1,79 @@ + 'uc_ajax_attach_multiplex', + 'list' => $list, + )); + //dpm($element); + } + } + + return $form; +} + +/** + * Ajax callback multiplexer. + * + * Processes a list of Ajax commands attached to the triggering element + * via hook_uc_ajax_attach(). + */ +function uc_ajax_attach_multiplex($form, $form_state) { + $element = $form_state['triggering_element']; + if (!empty($element['#ajax']['list'])) { + $commands = array(); + foreach ($element['#ajax']['list'] as $callback) { + if (!empty($callback) && function_exists($callback) && $result = $callback($form, $form_state)) { + if (is_array($result) && !empty($result['#type']) && $result['#type'] == 'ajax') { + // If the callback returned an array of commands, simply add these to the list. + $commands = array_merge($commands, $result['#commands']); + } + elseif (!empty($element['#ajax']['wrapper'])) { + // Otherwise, assume the callback returned a string or render-array, and insert it into the wrapper. + $html = is_string($result) ? $result : drupal_render($result); + $commands[] = ajax_command_replace('#' . $element['#ajax']['wrapper'], $html); + $commands[] = ajax_command_prepend('#' . $element['#ajax']['wrapper'], theme('status_messages')); + } + } + } + } + if (!empty($commands)) { + return array('#type' => 'ajax', '#commands' => $commands); + } +} + diff --git a/uc_store/uc_store.api.php b/uc_store/uc_store.api.php index 8689326..3d67765 100644 --- a/uc_store/uc_store.api.php +++ b/uc_store/uc_store.api.php @@ -11,6 +11,27 @@ */ /** + * Allows modules to add ajax callbacks to form elements in an orderly manner. The callbacks + * are invoked in module weight order to generate a list of ajax commands which are then returned to + * the page. Note that this hook is invoked before the form has been completely processed, so children + * of elements which are expanded (like 'radios') will not yet be present in the form. However, if you + * know the names of these elements, you may still specify ajax callbacks to be attached to them. + * + * @param $form + * The form to which ajax is being attached. + * @param $form_state + * The $form_state array. + * + * @return + * An array of ajax callbacks, keyed by the name of the element to which each should be attached. + */ +function hook_uc_ajax_attach($form, $form_state) { + if ($form['#form_id'] == 'uc_cart_checkout_form') { + return array('delivery_country' => 'mymodule_delivery_country_ajax'); + } +} + +/** * Allows modules to alter the TAPIr table after the rows are populated. * * The example below adds a value for the custom 'designer' column to the table diff --git a/uc_store/uc_store.module b/uc_store/uc_store.module index cada91c..9bb3ef7 100644 --- a/uc_store/uc_store.module +++ b/uc_store/uc_store.module @@ -224,6 +224,7 @@ function uc_store_admin_access() { */ function uc_store_init() { module_load_include('inc', 'uc_store', 'includes/tapir'); + module_load_include('inc', 'uc_store', 'includes/uc_ajax_attach'); global $conf; $conf['i18n_variables'][] = 'uc_store_name'; -- 1.7.3.4 From af4ee580554b608f7d9b101224c529690c3956b4 Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Sun, 18 Dec 2011 21:49:16 -0500 Subject: [PATCH 06/17] Issue #1373236: Require ajax attach callbacks to provide the parents of the element to which they want to attach ajax. --- payment/uc_payment/uc_payment_checkout_pane.inc | 8 ++++---- shipping/uc_quote/uc_quote.module | 12 +++++------- uc_store/includes/uc_ajax_attach.inc | 7 ++++--- uc_store/uc_store.api.php | 7 +++++-- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/payment/uc_payment/uc_payment_checkout_pane.inc b/payment/uc_payment/uc_payment_checkout_pane.inc index b12775a..a879483 100644 --- a/payment/uc_payment/uc_payment_checkout_pane.inc +++ b/payment/uc_payment/uc_payment_checkout_pane.inc @@ -146,10 +146,10 @@ function uc_payment_checkout_payment_details($form, $form_state) { */ function uc_payment_uc_ajax_attach($form, $form_state) { if ($form['#form_id'] == 'uc_cart_checkout_form') { - return array( - 'billing_country' => 'uc_payment_checkout_payment_methods', - 'delivery_country' => 'uc_payment_checkout_payment_methods', - ); + $fields = array(); + $fields['panes']['billing']['address']['billing_country'] = 'uc_payment_checkout_payment_methods'; + $fields['panes']['delivery']['address']['delivery_country'] = 'uc_payment_checkout_payment_methods'; + return $fields; } } diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index 714c1ea..92898ed 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -245,16 +245,14 @@ function uc_quote_form_alter(&$form, &$form_state, $form_id) { */ function uc_quote_uc_ajax_attach($form, $form_state) { if ($form['#form_id'] == 'uc_cart_checkout_form') { - $fields = array( - 'delivery_country' => 'uc_quote_checkout_returned_rates', - 'delivery_postal_code' => 'uc_quote_checkout_returned_rates', - 'select_address' => 'uc_quote_checkout_returned_rates', - ); + $fields = array(); + $fields['panes']['delivery']['address']['delivery_country'] = 'uc_quote_checkout_returned_rates'; + $fields['panes']['delivery']['address']['delivery_postal_code'] = 'uc_quote_checkout_returned_rates'; + $fields['panes']['delivery']['select_address'] = 'uc_quote_checkout_returned_rates'; if (isset($form['panes']['payment']['line_items'])) { - $fields['quote_option'] = 'uc_payment_get_totals'; + $fields['panes']['quotes']['quotes']['quote_option'] = 'uc_payment_get_totals'; } - return $fields; } } diff --git a/uc_store/includes/uc_ajax_attach.inc b/uc_store/includes/uc_ajax_attach.inc index 8d98247..eb3ce14 100644 --- a/uc_store/includes/uc_ajax_attach.inc +++ b/uc_store/includes/uc_ajax_attach.inc @@ -25,9 +25,10 @@ function uc_ajax_attach_process_form($form, &$form_state) { } } $element['#process'][] = 'uc_ajax_attach_process_form'; - - if (isset($fields[$child])) { - $list = is_array($fields[$child]) ? $fields[$child] : array($fields[$child]); + $parents = $form['#array_parents']; + array_push($parents, $child); + if ($list = drupal_array_get_nested_value($fields, $parents)) { + $list = is_array($list) ? $list : array($list); if (!empty($element['#ajax'])) { array_unshift($list, $element['#ajax']['callback']); diff --git a/uc_store/uc_store.api.php b/uc_store/uc_store.api.php index 3d67765..f43f9a5 100644 --- a/uc_store/uc_store.api.php +++ b/uc_store/uc_store.api.php @@ -23,11 +23,14 @@ * The $form_state array. * * @return - * An array of ajax callbacks, keyed by the name of the element to which each should be attached. + * An nested array of ajax callbacks. The keys of this array must match the parent keys of the element to which + * ajax is being attached. */ function hook_uc_ajax_attach($form, $form_state) { if ($form['#form_id'] == 'uc_cart_checkout_form') { - return array('delivery_country' => 'mymodule_delivery_country_ajax'); + $fields = array(); + $fields['panes']['delivery']['address']['delivery_country'] = 'mymodule_delivery_country_ajax'; + return $fields; } } -- 1.7.3.4 From 7fa0c2c47e88c4003acfdecd39bd403db6af8a5a Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Mon, 19 Dec 2011 11:58:24 -0500 Subject: [PATCH 07/17] Issue #1373236: Allow country change on order edit form to update available shipping quotes. --- shipping/uc_quote/uc_quote.module | 5 +++++ uc_order/uc_order.admin.inc | 2 +- 2 files changed, 6 insertions(+), 1 deletions(-) diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index 92898ed..9b2f407 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -255,6 +255,11 @@ function uc_quote_uc_ajax_attach($form, $form_state) { } return $fields; } + elseif ($form['#form_id'] == 'uc_order_edit_form') { + $fields = array(); + $fields['ship_to']['delivery_country'] = 'uc_quote_order_returned_rates'; + return $fields; + } } /** diff --git a/uc_order/uc_order.admin.inc b/uc_order/uc_order.admin.inc index 3cb5098..eabd150 100644 --- a/uc_order/uc_order.admin.inc +++ b/uc_order/uc_order.admin.inc @@ -1057,7 +1057,7 @@ function uc_order_edit_form($form, &$form_state, $order) { } field_attach_form('uc_order', $order, $form, $form_state); - + $form['#process'] = array('uc_ajax_attach_process_form'); return $form; } -- 1.7.3.4 From 33bcd27c81aa0581fb2ce2c95c408de6e2ba9827 Mon Sep 17 00:00:00 2001 From: Dave Long Date: Wed, 11 Apr 2012 22:45:03 +0100 Subject: [PATCH 08/17] Replace hook_uc_ajax_attach() with form state storage. --- payment/uc_payment/uc_payment_checkout_pane.inc | 15 ++------- shipping/uc_quote/uc_quote.module | 29 ++++------------- uc_cart/uc_cart.pages.inc | 2 +- uc_order/uc_order.admin.inc | 2 +- uc_store/includes/uc_ajax_attach.inc | 38 ++++++++++++----------- uc_store/uc_store.api.php | 24 -------------- 6 files changed, 32 insertions(+), 78 deletions(-) diff --git a/payment/uc_payment/uc_payment_checkout_pane.inc b/payment/uc_payment/uc_payment_checkout_pane.inc index a879483..091cd87 100644 --- a/payment/uc_payment/uc_payment_checkout_pane.inc +++ b/payment/uc_payment/uc_payment_checkout_pane.inc @@ -92,6 +92,9 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL $contents['details']['#markup'] = t('Continue with checkout to complete payment.'); } + $form_state['uc_ajax']['uc_payment']['panes']['billing']['address']['billing_country'] = 'uc_payment_checkout_payment_methods'; + $form_state['uc_ajax']['uc_payment']['panes']['delivery']['address']['delivery_country'] = 'uc_payment_checkout_payment_methods'; + return array('description' => $description, 'contents' => $contents); case 'process': @@ -142,18 +145,6 @@ function uc_payment_checkout_payment_details($form, $form_state) { } /** - * Implements hook_uc_ajax_attach(). - */ -function uc_payment_uc_ajax_attach($form, $form_state) { - if ($form['#form_id'] == 'uc_cart_checkout_form') { - $fields = array(); - $fields['panes']['billing']['address']['billing_country'] = 'uc_payment_checkout_payment_methods'; - $fields['panes']['delivery']['address']['delivery_country'] = 'uc_payment_checkout_payment_methods'; - return $fields; - } -} - -/** * AJAX callback for updating the list of payment methods. */ function uc_payment_checkout_payment_methods($form, $form_state) { diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index 9b2f407..63570c4 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -241,28 +241,6 @@ function uc_quote_form_alter(&$form, &$form_state, $form_id) { } /** - * Implements hook_uc_ajax_attach(). - */ -function uc_quote_uc_ajax_attach($form, $form_state) { - if ($form['#form_id'] == 'uc_cart_checkout_form') { - $fields = array(); - $fields['panes']['delivery']['address']['delivery_country'] = 'uc_quote_checkout_returned_rates'; - $fields['panes']['delivery']['address']['delivery_postal_code'] = 'uc_quote_checkout_returned_rates'; - $fields['panes']['delivery']['select_address'] = 'uc_quote_checkout_returned_rates'; - - if (isset($form['panes']['payment']['line_items'])) { - $fields['panes']['quotes']['quotes']['quote_option'] = 'uc_payment_get_totals'; - } - return $fields; - } - elseif ($form['#form_id'] == 'uc_order_edit_form') { - $fields = array(); - $fields['ship_to']['delivery_country'] = 'uc_quote_order_returned_rates'; - return $fields; - } -} - -/** * Implements hook_uc_cart_pane(). */ function uc_quote_uc_cart_pane($items) { @@ -598,6 +576,11 @@ function uc_checkout_pane_quotes($op, &$order, $form = NULL, &$form_state = NULL $contents['quotes'] += $order->quote_form; + $form_state['uc_ajax']['uc_quote']['panes']['delivery']['address']['delivery_country'] = 'uc_quote_checkout_returned_rates'; + $form_state['uc_ajax']['uc_quote']['panes']['delivery']['address']['delivery_postal_code'] = 'uc_quote_checkout_returned_rates'; + $form_state['uc_ajax']['uc_quote']['panes']['delivery']['select_address'] = 'uc_quote_checkout_returned_rates'; + $form_state['uc_ajax']['uc_quote']['panes']['quotes']['quotes']['quote_option'] = 'uc_payment_get_totals'; + return array('description' => $description, 'contents' => $contents); case 'prepare': @@ -727,6 +710,8 @@ function uc_order_pane_quotes($op, $order, &$form = NULL, &$form_state = NULL) { ); } + $form_state['uc_ajax']['uc_quote']['ship_to']['delivery_country'] = 'uc_quote_order_returned_rates'; + return $form; case 'edit-theme': diff --git a/uc_cart/uc_cart.pages.inc b/uc_cart/uc_cart.pages.inc index 262696c..e738238 100644 --- a/uc_cart/uc_cart.pages.inc +++ b/uc_cart/uc_cart.pages.inc @@ -279,7 +279,7 @@ function uc_cart_checkout_form($form, &$form_state, $order) { '#type' => 'submit', '#value' => t('Review order'), ); - $form['#process'] = array('uc_ajax_attach_process_form'); + $form['#process'][] = 'uc_ajax_process_form'; unset($_SESSION['uc_checkout'][$order->order_id]); diff --git a/uc_order/uc_order.admin.inc b/uc_order/uc_order.admin.inc index eabd150..84406ad 100644 --- a/uc_order/uc_order.admin.inc +++ b/uc_order/uc_order.admin.inc @@ -1057,7 +1057,7 @@ function uc_order_edit_form($form, &$form_state, $order) { } field_attach_form('uc_order', $order, $form, $form_state); - $form['#process'] = array('uc_ajax_attach_process_form'); + $form['#process'][] = 'uc_ajax_process_form'; return $form; } diff --git a/uc_store/includes/uc_ajax_attach.inc b/uc_store/includes/uc_ajax_attach.inc index eb3ce14..19f19ff 100644 --- a/uc_store/includes/uc_ajax_attach.inc +++ b/uc_store/includes/uc_ajax_attach.inc @@ -1,13 +1,8 @@ $fields) { + $callback = drupal_array_get_nested_value($fields, $parents); + if (is_string($callback)) { + $callbacks[] = $callback; + } + } + + if ($callbacks) { if (!empty($element['#ajax'])) { - array_unshift($list, $element['#ajax']['callback']); + array_unshift($callbacks, $element['#ajax']['callback']); } else { $element['#ajax'] = array(); } $element['#ajax'] = array_merge($element['#ajax'], array( - 'callback' => 'uc_ajax_attach_multiplex', - 'list' => $list, + 'callback' => 'uc_ajax_multiplex', + 'list' => $callbacks, )); - //dpm($element); } } @@ -51,10 +54,9 @@ function uc_ajax_attach_process_form($form, &$form_state) { /** * Ajax callback multiplexer. * - * Processes a list of Ajax commands attached to the triggering element - * via hook_uc_ajax_attach(). + * Processes a set of Ajax commands attached to the triggering element. */ -function uc_ajax_attach_multiplex($form, $form_state) { +function uc_ajax_multiplex($form, $form_state) { $element = $form_state['triggering_element']; if (!empty($element['#ajax']['list'])) { $commands = array(); diff --git a/uc_store/uc_store.api.php b/uc_store/uc_store.api.php index f43f9a5..8689326 100644 --- a/uc_store/uc_store.api.php +++ b/uc_store/uc_store.api.php @@ -11,30 +11,6 @@ */ /** - * Allows modules to add ajax callbacks to form elements in an orderly manner. The callbacks - * are invoked in module weight order to generate a list of ajax commands which are then returned to - * the page. Note that this hook is invoked before the form has been completely processed, so children - * of elements which are expanded (like 'radios') will not yet be present in the form. However, if you - * know the names of these elements, you may still specify ajax callbacks to be attached to them. - * - * @param $form - * The form to which ajax is being attached. - * @param $form_state - * The $form_state array. - * - * @return - * An nested array of ajax callbacks. The keys of this array must match the parent keys of the element to which - * ajax is being attached. - */ -function hook_uc_ajax_attach($form, $form_state) { - if ($form['#form_id'] == 'uc_cart_checkout_form') { - $fields = array(); - $fields['panes']['delivery']['address']['delivery_country'] = 'mymodule_delivery_country_ajax'; - return $fields; - } -} - -/** * Allows modules to alter the TAPIr table after the rows are populated. * * The example below adds a value for the custom 'designer' column to the table -- 1.7.3.4 From 2c0f437b8b08416657b0dc0a5468784342c39034 Mon Sep 17 00:00:00 2001 From: Dave Long Date: Wed, 11 Apr 2012 23:01:43 +0100 Subject: [PATCH 09/17] Use form_load_include() for Ajax forms. --- uc_cart/uc_cart.pages.inc | 2 ++ uc_order/uc_order.admin.inc | 3 +++ uc_store/uc_store.module | 1 - 3 files changed, 5 insertions(+), 1 deletions(-) diff --git a/uc_cart/uc_cart.pages.inc b/uc_cart/uc_cart.pages.inc index e738238..2c476d6 100644 --- a/uc_cart/uc_cart.pages.inc +++ b/uc_cart/uc_cart.pages.inc @@ -279,6 +279,8 @@ function uc_cart_checkout_form($form, &$form_state, $order) { '#type' => 'submit', '#value' => t('Review order'), ); + + form_load_include($form_state, 'inc', 'uc_store', 'includes/uc_ajax_attach'); $form['#process'][] = 'uc_ajax_process_form'; unset($_SESSION['uc_checkout'][$order->order_id]); diff --git a/uc_order/uc_order.admin.inc b/uc_order/uc_order.admin.inc index 84406ad..e0b1507 100644 --- a/uc_order/uc_order.admin.inc +++ b/uc_order/uc_order.admin.inc @@ -1057,7 +1057,10 @@ function uc_order_edit_form($form, &$form_state, $order) { } field_attach_form('uc_order', $order, $form, $form_state); + + form_load_include($form_state, 'inc', 'uc_store', 'includes/uc_ajax_attach'); $form['#process'][] = 'uc_ajax_process_form'; + return $form; } diff --git a/uc_store/uc_store.module b/uc_store/uc_store.module index 9bb3ef7..cada91c 100644 --- a/uc_store/uc_store.module +++ b/uc_store/uc_store.module @@ -224,7 +224,6 @@ function uc_store_admin_access() { */ function uc_store_init() { module_load_include('inc', 'uc_store', 'includes/tapir'); - module_load_include('inc', 'uc_store', 'includes/uc_ajax_attach'); global $conf; $conf['i18n_variables'][] = 'uc_store_name'; -- 1.7.3.4 From 7bdecfb1cd28e300d731dea4604667166bdf7ad9 Mon Sep 17 00:00:00 2001 From: Dave Long Date: Wed, 11 Apr 2012 23:32:10 +0100 Subject: [PATCH 10/17] Skip uc_ajax processing if no fields need it. --- uc_store/includes/uc_ajax_attach.inc | 4 ++++ 1 files changed, 4 insertions(+), 0 deletions(-) diff --git a/uc_store/includes/uc_ajax_attach.inc b/uc_store/includes/uc_ajax_attach.inc index 19f19ff..2111339 100644 --- a/uc_store/includes/uc_ajax_attach.inc +++ b/uc_store/includes/uc_ajax_attach.inc @@ -3,6 +3,10 @@ * Form process callback to allow multiple Ajax callbacks on form elements. */ function uc_ajax_process_form($form, &$form_state) { + if (!isset($form_state['uc_ajax'])) { + return $form; + } + // We have to operate on the children rather than on the element itself, as // #process functions are called *after* form_handle_input_elements(), // which is where the triggering element is determined. If we haven't added -- 1.7.3.4 From 7c3190d523cecb49a9c2457c63e86dc31433912d Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Sun, 15 Apr 2012 14:58:35 -0400 Subject: [PATCH 11/17] Convert nested arrays in uc_ajax to arrays with a single key for each element a la form-set-error. --- payment/uc_payment/uc_payment_checkout_pane.inc | 4 ++-- shipping/uc_quote/uc_quote.module | 10 +++++----- uc_store/includes/uc_ajax_attach.inc | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/payment/uc_payment/uc_payment_checkout_pane.inc b/payment/uc_payment/uc_payment_checkout_pane.inc index 091cd87..a4e9c0f 100644 --- a/payment/uc_payment/uc_payment_checkout_pane.inc +++ b/payment/uc_payment/uc_payment_checkout_pane.inc @@ -92,8 +92,8 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL $contents['details']['#markup'] = t('Continue with checkout to complete payment.'); } - $form_state['uc_ajax']['uc_payment']['panes']['billing']['address']['billing_country'] = 'uc_payment_checkout_payment_methods'; - $form_state['uc_ajax']['uc_payment']['panes']['delivery']['address']['delivery_country'] = 'uc_payment_checkout_payment_methods'; + $form_state['uc_ajax']['uc_payment']['panes][billing][address][billing_country'] = 'uc_payment_checkout_payment_methods'; + $form_state['uc_ajax']['uc_payment']['panes][delivery][address][delivery_country'] = 'uc_payment_checkout_payment_methods'; return array('description' => $description, 'contents' => $contents); diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index 63570c4..2230134 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -576,10 +576,10 @@ function uc_checkout_pane_quotes($op, &$order, $form = NULL, &$form_state = NULL $contents['quotes'] += $order->quote_form; - $form_state['uc_ajax']['uc_quote']['panes']['delivery']['address']['delivery_country'] = 'uc_quote_checkout_returned_rates'; - $form_state['uc_ajax']['uc_quote']['panes']['delivery']['address']['delivery_postal_code'] = 'uc_quote_checkout_returned_rates'; - $form_state['uc_ajax']['uc_quote']['panes']['delivery']['select_address'] = 'uc_quote_checkout_returned_rates'; - $form_state['uc_ajax']['uc_quote']['panes']['quotes']['quotes']['quote_option'] = 'uc_payment_get_totals'; + $form_state['uc_ajax']['uc_quote']['panes][delivery][address][delivery_country'] = 'uc_quote_checkout_returned_rates'; + $form_state['uc_ajax']['uc_quote']['panes][delivery][address][delivery_postal_code'] = 'uc_quote_checkout_returned_rates'; + $form_state['uc_ajax']['uc_quote']['panes][delivery][select_address'] = 'uc_quote_checkout_returned_rates'; + $form_state['uc_ajax']['uc_quote']['panes][quotes][quotes][quote_option'] = 'uc_payment_get_totals'; return array('description' => $description, 'contents' => $contents); @@ -710,7 +710,7 @@ function uc_order_pane_quotes($op, $order, &$form = NULL, &$form_state = NULL) { ); } - $form_state['uc_ajax']['uc_quote']['ship_to']['delivery_country'] = 'uc_quote_order_returned_rates'; + $form_state['uc_ajax']['uc_quote']['ship_to][delivery_country'] = 'uc_quote_order_returned_rates'; return $form; diff --git a/uc_store/includes/uc_ajax_attach.inc b/uc_store/includes/uc_ajax_attach.inc index 2111339..20fb561 100644 --- a/uc_store/includes/uc_ajax_attach.inc +++ b/uc_store/includes/uc_ajax_attach.inc @@ -28,12 +28,12 @@ function uc_ajax_process_form($form, &$form_state) { // Multiplex any Ajax calls for this element. $parents = $form['#array_parents']; array_push($parents, $child); + $key = implode('][', $parents); $callbacks = array(); foreach ($form_state['uc_ajax'] as $module => $fields) { - $callback = drupal_array_get_nested_value($fields, $parents); - if (is_string($callback)) { - $callbacks[] = $callback; + if (!empty($fields[$key])) { + $callbacks[] = $fields[$key]; } } -- 1.7.3.4 From 1d32b4cc034bd9773a46dc8fa19246cd75433d04 Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Wed, 18 Apr 2012 13:14:28 -0400 Subject: [PATCH 12/17] Allow modules to specify multiple ajax callbacks and/or key callbacks by wrapper. --- uc_store/includes/uc_ajax_attach.inc | 129 +++++++++++++++++++++++++++++++--- 1 files changed, 118 insertions(+), 11 deletions(-) diff --git a/uc_store/includes/uc_ajax_attach.inc b/uc_store/includes/uc_ajax_attach.inc index 20fb561..37610de 100644 --- a/uc_store/includes/uc_ajax_attach.inc +++ b/uc_store/includes/uc_ajax_attach.inc @@ -1,5 +1,72 @@ 'uc_ajax_replace_checkout_pane', + * ); + * @endcode + * + * This will cause the contents of 'quotes-pane' to be replaced by the return + * value of uc_ajax_replace_checkout_pane(). Note that if more than one module + * assign a callback to the same wrapper key, the heavier module or pane will + * take precedence. + * + * Implementors need not provide a wrapper key for each callback, in which case + * the callback must return an array of ajax commands rather than a renderable + * form element. For example: + * + * @code + * $form_state['uc_ajax']['mymodule']['panes][quotes][quote_button'] = array('my_ajax_callback'); + * ... + * function my_ajax_callback($form, $form_state) { + * $commands[] = ajax_command_invoke('#my-input-element', 'val', 0); + * return array('#type' => 'ajax', '#commands' => $commands); + * } + * @endcode + * + * However, using a wrapper key where appropriate will reduce redundant + * replacements of the same element. + * + * NOTE: 'uc_ajax_replace_checkout_pane' is a convenience callback which will + * replace the contents of an entire checkout pane. It is generally preferable + * to use this when updating data on the checkout form, as this will + * further reduce the likelihood of redundant replacements. You should use + * your own callback only when behaviours other than replacement are + * desired, or when replacing data that lie outside a checkout pane. Note + * also that you may combine both formulations by mixing numeric and string keys. + * For example: + * + * @code + * $form_state['uc_ajax']['mymodule']['panes][quotes][quote_button'] = array( + * 'my_ajax_callback', + * 'quotes-pane' => 'uc_ajax_replace_checkout_pane', + * ); + * @endcode + */ + +/** * Form process callback to allow multiple Ajax callbacks on form elements. */ function uc_ajax_process_form($form, &$form_state) { @@ -33,17 +100,27 @@ function uc_ajax_process_form($form, &$form_state) { $callbacks = array(); foreach ($form_state['uc_ajax'] as $module => $fields) { if (!empty($fields[$key])) { - $callbacks[] = $fields[$key]; + if (is_array($fields[$key])) { + $callbacks = array_merge($callbacks, $fields[$key]); + } + else { + $callbacks[] = $fields[$key]; + } } } - if ($callbacks) { - if (!empty($element['#ajax'])) { - array_unshift($callbacks, $element['#ajax']['callback']); - } - else { + if (!empty($callbacks)) { + if (empty($element['#ajax'])) { $element['#ajax'] = array(); } + elseif (!empty($element['#ajax']['callback'])) { + if (!empty($element['#ajax']['wrapper'])) { + $callbacks[$element['#ajax']['wrapper']] = $element['#ajax']['callback']; + } + else { + array_unshift($callbacks, $element['#ajax']['callback']); + } + } $element['#ajax'] = array_merge($element['#ajax'], array( 'callback' => 'uc_ajax_multiplex', @@ -64,17 +141,17 @@ function uc_ajax_multiplex($form, $form_state) { $element = $form_state['triggering_element']; if (!empty($element['#ajax']['list'])) { $commands = array(); - foreach ($element['#ajax']['list'] as $callback) { - if (!empty($callback) && function_exists($callback) && $result = $callback($form, $form_state)) { + foreach ($element['#ajax']['list'] as $wrapper => $callback) { + if (!empty($callback) && function_exists($callback) && $result = $callback($form, $form_state, $wrapper)) { if (is_array($result) && !empty($result['#type']) && $result['#type'] == 'ajax') { // If the callback returned an array of commands, simply add these to the list. $commands = array_merge($commands, $result['#commands']); } - elseif (!empty($element['#ajax']['wrapper'])) { + elseif (is_string($wrapper)) { // Otherwise, assume the callback returned a string or render-array, and insert it into the wrapper. $html = is_string($result) ? $result : drupal_render($result); - $commands[] = ajax_command_replace('#' . $element['#ajax']['wrapper'], $html); - $commands[] = ajax_command_prepend('#' . $element['#ajax']['wrapper'], theme('status_messages')); + $commands[] = ajax_command_replace('#' . $wrapper, $html); + $commands[] = ajax_command_prepend('#' . $wrapper, theme('status_messages')); } } } @@ -84,3 +161,33 @@ function uc_ajax_multiplex($form, $form_state) { } } +/** + * Ajax callback to replace a whole checkout pane. + * + * @param $form + * The checkout form. + * @param $form_state + * The current form state. + * @param $wrapper + * Special third parameter passed for uc_ajax callbacks containing the ajax + * wrapper for this callback. Here used to determine which pane to replace. + * + * @return + * The form element representing the pane, suitable for ajax rendering. If + * the pane does not exist, or if the wrapper does not refer to a checkout + * pane, returns nothing. + */ +function uc_ajax_replace_checkout_pane($form, $form_state, $wrapper = NULL) { + if (empty($wrapper) && !empty($form_state['triggering_element']['#ajax']['wrapper'])) { + // If $wrapper is absent, then we were not invoked by uc_ajax_multiplex, + // so try to use the wrapper of the triggering element's #ajax array. + $wrapper = $form_state['triggering_element']['#ajax']['wrapper']; + } + if (!empty($wrapper)) { + list($pane, $verify) = explode('-', $wrapper); + if ($verify === 'pane' && !empty($form['panes'][$pane])) { + return $form['panes'][$pane]; + } + } +} + -- 1.7.3.4 From 7d38d9d68768af8e8a66b6d31a1948ecd1f8669d Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Wed, 18 Apr 2012 13:57:33 -0400 Subject: [PATCH 13/17] Update core modules to provide ajax callbacks keyed by pane. --- payment/uc_payment/uc_payment_checkout_pane.inc | 16 +------ shipping/uc_quote/uc_quote.module | 47 +++++++++-------------- 2 files changed, 21 insertions(+), 42 deletions(-) diff --git a/payment/uc_payment/uc_payment_checkout_pane.inc b/payment/uc_payment/uc_payment_checkout_pane.inc index a4e9c0f..f0aae20 100644 --- a/payment/uc_payment/uc_payment_checkout_pane.inc +++ b/payment/uc_payment/uc_payment_checkout_pane.inc @@ -92,8 +92,9 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL $contents['details']['#markup'] = t('Continue with checkout to complete payment.'); } - $form_state['uc_ajax']['uc_payment']['panes][billing][address][billing_country'] = 'uc_payment_checkout_payment_methods'; - $form_state['uc_ajax']['uc_payment']['panes][delivery][address][delivery_country'] = 'uc_payment_checkout_payment_methods'; + $ajax_attach = array('payment-pane' => 'uc_ajax_replace_checkout_pane'); + $form_state['uc_ajax']['uc_payment']['panes][billing][address][billing_country'] = $ajax_attach; + $form_state['uc_ajax']['uc_payment']['panes][delivery][address][delivery_country'] = $ajax_attach; return array('description' => $description, 'contents' => $contents); @@ -143,14 +144,3 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL function uc_payment_checkout_payment_details($form, $form_state) { return $form['panes']['payment']['details']; } - -/** - * AJAX callback for updating the list of payment methods. - */ -function uc_payment_checkout_payment_methods($form, $form_state) { - $commands[] = ajax_command_replace('#payment-method', drupal_render($form['panes']['payment']['payment_method'])); - $commands[] = ajax_command_replace('#payment-details', drupal_render($form['panes']['payment']['details'])); - $commands[] = ajax_command_prepend('#payment-method', theme('status_messages')); - - return array('#type' => 'ajax', '#commands' => $commands); -} diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index 2230134..3fa49f1 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -557,7 +557,9 @@ function uc_checkout_pane_quotes($op, &$order, $form = NULL, &$form_state = NULL '#submit' => array('uc_quote_checkout_pane_quotes_submit'), '#weight' => 0, '#ajax' => array( - 'callback' => 'uc_quote_checkout_returned_rates', + // The callbacks are set below in $form_state['uc_ajax_attach'] + //'callback' => 'uc_ajax_replace_checkout_pane', + //'wrapper' => 'quotes-pane', 'effect' => 'slide', 'progress' => array( 'type' => 'bar', @@ -576,10 +578,15 @@ function uc_checkout_pane_quotes($op, &$order, $form = NULL, &$form_state = NULL $contents['quotes'] += $order->quote_form; - $form_state['uc_ajax']['uc_quote']['panes][delivery][address][delivery_country'] = 'uc_quote_checkout_returned_rates'; - $form_state['uc_ajax']['uc_quote']['panes][delivery][address][delivery_postal_code'] = 'uc_quote_checkout_returned_rates'; - $form_state['uc_ajax']['uc_quote']['panes][delivery][select_address'] = 'uc_quote_checkout_returned_rates'; - $form_state['uc_ajax']['uc_quote']['panes][quotes][quotes][quote_option'] = 'uc_payment_get_totals'; + $ajax_attach = array( + 'payment-pane' => 'uc_ajax_replace_checkout_pane', + 'quotes-pane' => 'uc_ajax_replace_checkout_pane' + ); + $form_state['uc_ajax']['uc_quote']['panes][quotes][quote_button'] = $ajax_attach; + $form_state['uc_ajax']['uc_quote']['panes][delivery][address][delivery_country'] = $ajax_attach; + $form_state['uc_ajax']['uc_quote']['panes][delivery][address][delivery_postal_code'] = $ajax_attach; + $form_state['uc_ajax']['uc_quote']['panes][delivery][select_address'] = $ajax_attach; + $form_state['uc_ajax']['uc_quote']['panes][quotes][quotes][quote_option'] = array('payment-pane' => 'uc_ajax_replace_checkout_pane'); return array('description' => $description, 'contents' => $contents); @@ -686,7 +693,8 @@ function uc_order_pane_quotes($op, $order, &$form = NULL, &$form_state = NULL) { '#value' => t('Get shipping quotes'), '#submit' => array('uc_quote_order_pane_quotes_submit'), '#ajax' => array( - 'callback' => 'uc_quote_order_returned_rates', + 'callback' => 'uc_quote_replace_order_quotes', + 'wrapper' => 'quote', 'effect' => 'slide', 'progress' => array( 'type' => 'bar', @@ -710,7 +718,7 @@ function uc_order_pane_quotes($op, $order, &$form = NULL, &$form_state = NULL) { ); } - $form_state['uc_ajax']['uc_quote']['ship_to][delivery_country'] = 'uc_quote_order_returned_rates'; + $form_state['uc_ajax']['uc_quote']['ship_to][delivery_country'] = array('quote' => 'uc_quote_replace_order_quotes'); return $form; @@ -881,29 +889,10 @@ function _uc_quote_extract_default_option($quote_form) { } /** - * AJAX callback for calculated shipping rates. + * Ajax callback to update the quotes on the order edit form. */ -function uc_quote_checkout_returned_rates($form, $form_state) { - $commands[] = ajax_command_replace('#quote', drupal_render($form['panes']['quotes']['quotes'])); - $commands[] = ajax_command_prepend('#quote', theme('status_messages')); - - // Show default shipping rate as a line item. - if (isset($form['panes']['payment']['line_items'])) { - $commands[] = ajax_command_replace('#line-items-div', drupal_render($form['panes']['payment']['line_items'])); - $commands[] = ajax_command_prepend('#line-items-div', theme('status_messages')); - } - - return array('#type' => 'ajax', '#commands' => $commands); -} - -/** - * AJAX callback for calculated shipping rates. - */ -function uc_quote_order_returned_rates($form, $form_state) { - $commands[] = ajax_command_replace('#quote', drupal_render($form['quotes']['quotes'])); - $commands[] = ajax_command_prepend('#quote', theme('status_messages')); - - return array('#type' => 'ajax', '#commands' => $commands); +function uc_quote_replace_order_quotes($form, $form_state) { + return $form['quotes']['quotes']; } /** -- 1.7.3.4 From b9880cdaaa32b4185aa82cd2b3197a9b796a2f0e Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Thu, 19 Apr 2012 12:33:37 -0500 Subject: [PATCH 14/17] Back-end for admin configured ajax on the checkout page. --- uc_store/includes/uc_ajax_attach.inc | 32 ++++++++++++++++++++++++++++++++ 1 files changed, 32 insertions(+), 0 deletions(-) diff --git a/uc_store/includes/uc_ajax_attach.inc b/uc_store/includes/uc_ajax_attach.inc index 37610de..fb7a248 100644 --- a/uc_store/includes/uc_ajax_attach.inc +++ b/uc_store/includes/uc_ajax_attach.inc @@ -70,6 +70,21 @@ * Form process callback to allow multiple Ajax callbacks on form elements. */ function uc_ajax_process_form($form, &$form_state) { + // When processing the top level form, add any variable-defined pane wrappers. + if (isset($form['#form_id'])) { + switch ($form['#form_id']) { + case 'uc_cart_checkout_form': + $config = variable_get('uc_ajax_checkout', _uc_ajax_defaults('checkout')); + foreach ($config as $key => $panes) { + foreach (array_keys($panes) as $pane) { + $config[$key][$pane] = 'uc_ajax_replace_checkout_pane'; + } + } + $form_state['uc_ajax']['uc_ajax'] = $config; + break; + } + } + if (!isset($form_state['uc_ajax'])) { return $form; } @@ -191,3 +206,20 @@ function uc_ajax_replace_checkout_pane($form, $form_state, $wrapper = NULL) { } } +/** + * Retrieve the default ajax behaviors for a target form. + * + * @param $target_form + * The form whose default behaviors are to be retrieved. + * + * @return + * The array of default behaviors for the form. + */ +function _uc_ajax_defaults($target_form) { + switch ($target_form) { + case 'checkout': + return array(); + default: + return array(); + } +} -- 1.7.3.4 From 9bd84820eba90544986eea562cb1349103255751 Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Thu, 19 Apr 2012 13:03:56 -0500 Subject: [PATCH 15/17] uc_ajax_admin: Front-end for admin configured ajax on the checkout page. --- uc_ajax_admin/uc_ajax_admin.info | 5 + uc_ajax_admin/uc_ajax_admin.module | 243 ++++++++++++++++++++++++++++++++++++ 2 files changed, 248 insertions(+), 0 deletions(-) create mode 100644 uc_ajax_admin/uc_ajax_admin.info create mode 100644 uc_ajax_admin/uc_ajax_admin.module diff --git a/uc_ajax_admin/uc_ajax_admin.info b/uc_ajax_admin/uc_ajax_admin.info new file mode 100644 index 0000000..edba546 --- /dev/null +++ b/uc_ajax_admin/uc_ajax_admin.info @@ -0,0 +1,5 @@ +name = Ubercart Ajax Administration +description = Administrative interface for ajax updates to Ubercart forms. +dependencies[] = uc_cart +package = Ubercart - extra +core = 7.x diff --git a/uc_ajax_admin/uc_ajax_admin.module b/uc_ajax_admin/uc_ajax_admin.module new file mode 100644 index 0000000..1ae9557 --- /dev/null +++ b/uc_ajax_admin/uc_ajax_admin.module @@ -0,0 +1,243 @@ + 'Ajax', + 'description' => 'Administer ajax updates on checkout form.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('uc_ajax_admin_form', 'checkout'), + 'access arguments' => array('administer store'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 3, + ); + return $items; +} + +/** + * Administration form for uc_ajax. + * + * @param $target_form + * The form for which ajax behaviors are to be administered. Currently only + * 'checkout' is supported. + */ +function uc_ajax_admin_form($form, &$form_state, $target_form = 'checkout') { + module_load_include('inc', 'uc_store', 'includes/uc_ajax_attach'); + switch ($target_form) { + case 'checkout': + $triggers = _uc_ajax_admin_checkout_trigger_options(_uc_ajax_admin_build_checkout_form()); + $panes = _uc_checkout_pane_list(); + $wrappers = array(); + foreach ($panes as $id => $pane) { + $wrappers["$id-pane"] = _uc_checkout_pane_data($id, 'title'); + } + break; + + default: + drupal_not_found(); + } + $form['#uc_ajax_target'] = $target_form; + $form['#uc_ajax_config'] = variable_get('uc_ajax_' . $target_form, _uc_ajax_defaults($target_form)); + + $instructions = '

' . t('Use this page to configure "Ajax" behaviors on the @target_form form. The table below + associates triggering form input elements with @target_form panes. The contents of each associated pane + will be refreshed whenever the customer clicks on or changes the triggering form element. For example, you + can cause the available payment methods to be refreshed when the customer changes their billing zone.', + array('@target_form' => $target_form)) + . '

'; + $instructions .= '

' . t("Note that the triggering elements you can choose are listed based on the + @target_form form as it would be displayed to you right now. For example, if none of your shipping methods + apply to the current cart contents, you won't see the shipping quote selection element. If you don't see + the form element you wish to use as a trigger, try adding some products to the shopping cart or otherwise + simulating the customer experience, and verify that those elements are present on the form itself.", + array('@target_form' => $target_form)) + . '

'; + + $form['instructions'] = array( + '#type' => 'item', + '#title' => t('Checkout form Ajax Behaviors'), + '#markup' => $instructions, + ); + $form['table'] = tapir_get_table('uc_ajax_admin_table', $triggers, $wrappers, $form['#uc_ajax_config']); + $form['actions'] = array( + '#type' => 'actions', + 'submit' => array( + '#type' => 'submit', + '#value' => t('Submit'), + ), + ); + return $form; +} + +/** + * Submit handler for the uc_ajax_admin form. + */ +function uc_ajax_admin_form_submit($form, &$form_state) { + $config = $form['#uc_ajax_config']; + foreach ($form_state['values']['table'] as $index => $entry) { + $key = $entry['key']; + if ($index === '_new') { + if (!empty($key) && !empty($entry['panes'])) { + $config[$key] = $entry['panes']; + } + } + elseif ($entry['remove'] || empty($entry['panes'])) { + unset($config[$key]); + } + else { + $config[$key] = $entry['panes']; + } + } + variable_set('uc_ajax_' . $form['#uc_ajax_target'], $config); + drupal_set_message(t('Your changes have been saved.')); +} + +/** + * TAPIr table callback for the uc_ajax administrative form. + * + * @param $trigger_options + * The select options for triggering elements. + * @param $wrapper_options + * The select options for wrappers. + * @param $config + * The existing configuration. + */ +function uc_ajax_admin_table($trigger_options, $wrapper_options, $config) { + $rows = array(); + foreach ($config as $key => $panes) { + list(, $pane) = explode('][', $key); + $rows[] = array( + 'key' => array( + '#type' => 'hidden', + '#value' => $key, + '#suffix' => empty($trigger_options[ucfirst($pane)][$key]) ? $key : ucfirst($pane) . ': ' . $trigger_options[ucfirst($pane)][$key], + ), + 'panes' => array( + '#type' => 'select', + '#options' => $wrapper_options, + '#default_value' => $panes, + '#multiple' => TRUE, + ), + 'remove' => array( + '#type' => 'checkbox', + '#default_value' => FALSE, + ), + ); + } + $rows['_new'] = array( + 'key' => array( + '#type' => 'select', + '#options' => array(0 => t('--Add a new element--')) + $trigger_options, + ), + 'panes' => array( + '#type' => 'select', + '#options' => $wrapper_options, + '#multiple' => TRUE, + ), + 'remove' => array( + '#type' => 'hidden', + '#value' => 0, + ), + ); + + $table = array( + '#type' => 'tapir_table', + '#tree' => TRUE, + '#columns' => array( + 'remove' => array( + 'cell' => t('Remove'), + 'weight' => 3, + ), + 'key' => array( + 'cell' => t('Triggering form element'), + 'weight' => 1, + ), + 'panes' => array( + 'cell' => t('Panes to update'), + 'weight' => 2, + ), + ), + ) + $rows; + + return $table; +} + +/** + * Recursively builds a list of all form elements which are suitable triggers + * for ajax updates. + * + * @param $element + * The element to check. + * @param $list + * The list being built. When complete will be an array of the form + * 'element_name' => 'Element title' + * where 'element_name' is the name of the element as would be specified for + * form_set_error(), and 'Element title' is a best guess at the human readable + * name of the element. + */ +function _uc_ajax_admin_list_triggers($element, &$list) { + if (!empty($element['#input']) && !in_array($element['#type'], array('hidden', 'uc_address'))) { + $key = implode('][', $element['#array_parents']); + switch ($element['#type']) { + case 'button': case 'submit': + $title = empty($element['#value']) ? $key : $element['#value']; + break; + default: + $title = empty($element['#title']) ? $key : $element['#title']; + } + $list[$key] = $title; + } + if (empty($element['#type']) || !in_array($element['#type'], array('radios', 'checkboxes'))) { + foreach (element_children($element) as $child) { + _uc_ajax_admin_list_triggers($element[$child], $list); + } + } +} + +/** + * Builds a hierarchical list of possible ajax triggers for the checkout form. + * + * @param $form + * The fully processed checkout form to search for triggers. + * + * @return + * An hierarchical array of select options, categorized by pane. + */ +function _uc_ajax_admin_checkout_trigger_options($form) { + $list = array(); + foreach (element_children($form['panes']) as $name) { + $group = ucfirst($name); + $list[$group] = array(); + _uc_ajax_admin_list_triggers($form['panes'][$name], $list[$group]); + if (empty($list[$group])) { + unset($list[$group]); + } + } + return $list; +} + +/** + * Builds the checkout form, using the cart order if it exists, or a default + * shippable order if not. + */ +function _uc_ajax_admin_build_checkout_form() { + module_load_include('inc', 'uc_cart', 'uc_cart.pages'); + $order = empty($_SESSION['cart_order']) ? FALSE : uc_order_load($_SESSION['cart_order']); + if (!$order) { + $order = new UcOrder(); + $order->products = array((object) array( + 'cart_item_id' => 0, + 'title' => 'fake', + 'nid' => 0, + 'qty' => 1, + 'price' => 1, + 'data' => array('shippable' => TRUE), + 'model' => 0, + 'weight' => 0 + )); + } + return drupal_get_form('uc_cart_checkout_form', $order); +} -- 1.7.3.4 From 58e015dc81bce5aa829c3e45acf78f6e14090ea8 Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Thu, 19 Apr 2012 13:04:28 -0500 Subject: [PATCH 16/17] Tests for checkout ajax. --- uc_store/tests/uc_ajax.test | 103 +++++++++++++++++++++++++++++++++++++++++++ uc_store/uc_store.info | 1 + 2 files changed, 104 insertions(+), 0 deletions(-) create mode 100644 uc_store/tests/uc_ajax.test diff --git a/uc_store/tests/uc_ajax.test b/uc_store/tests/uc_ajax.test new file mode 100644 index 0000000..dbbf8a3 --- /dev/null +++ b/uc_store/tests/uc_ajax.test @@ -0,0 +1,103 @@ + 'Ajax functionality', + 'description' => 'Ajax update of checkout and order pages.', + 'group' => 'Ubercart', + ); + } + + /** + * Overrides DrupalWebTestCase::setUp(). + */ + public function setUp() { + module_load_include('inc', 'uc_store', 'includes/uc_ajax_attach'); + $modules = array('rules_admin', 'uc_payment', 'uc_payment_pack'); + $permissions = array('administer rules', 'bypass rules access'); + parent::setUp($modules, $permissions); + $this->drupalLogin($this->adminUser); + } + + /** + * Set a zone-based condition for a particular payment method. + * + * @param $method + * The method to set (e.g. 'check') + * @param $zone + * The zone id (numeric) to check for. + * @param $negate + * TRUE to negate the condition. + */ + function addPaymentZoneCondition($method, $zone, $negate = FALSE) { + $not = $negate ? 'NOT ' : ''; + $name = 'uc_payment_method_' . $method; + $label = ucfirst($method) . ' conditions'; + $condition = array( + 'LABEL' => $label, + 'PLUGIN' => 'and', + 'REQUIRES' => array('rules'), + 'USES VARIABLES' => array( + 'order' => array( + 'label' => 'Order', + 'type' => 'uc_order', + ), + ), + 'AND' => array( + array( + $not . 'data_is' => array( + 'data' => array('order:billing-address:zone'), + 'value' => $zone, + ), + ), + ), + ); + $newconfig = rules_import(array($name => $condition)); + $oldconfig = rules_config_load($name); + if ($oldconfig) { + $newconfig->id = $oldconfig->id; + unset($newconfig->is_new); + $newconfig->status = ENTITY_CUSTOM; + } + $newconfig->save(); + entity_flush_caches(); + //$this->drupalGet('admin/config/workflow/rules/components/manage/' . $newconfig->id); + } + + function testCheckoutAjax() { + // Enable two payment methods and set a condition on one. + variable_set('uc_payment_method_check_checkout', TRUE); + variable_set('uc_payment_method_other_checkout', TRUE); + $this->addPaymentZoneCondition('other', '26'); + + // Speciy that the billing zone should update the payment pane. + $config = variable_get('uc_ajax_checkout', _uc_ajax_defaults('checkout')); + $config['panes][billing][address][billing_zone'] = array('payment-pane' => 'payment-pane'); + variable_set('uc_ajax_checkout', $config); + + // Go to the checkout page, veriy that the conditional payment method is + // not available. + $product = $this->createProduct(array('shippable' => FALSE)); + $this->drupalPost('node/' . $product->nid, array(), t('Add to cart')); + $this->drupalPost('cart', array('items[0][qty]' => 1), t('Checkout')); + $this->assertNoText('Other'); + + // Change the billing zone and veriy that payment pane updates. + $edit = array(); + $edit['panes[billing][billing_zone]'] = '26'; + $result = $this->ucPostAjax(NULL, $edit, 'panes[billing][billing_zone]'); + $this->assertText("Other"); + $edit['panes[billing][billing_zone]'] = '1'; + $result = $this->ucPostAjax(NULL, $edit, 'panes[billing][billing_zone]'); + // Not in Kansas any more... + $this->assertNoText("Other"); + } +} diff --git a/uc_store/uc_store.info b/uc_store/uc_store.info index 508c2bd..3e90801 100644 --- a/uc_store/uc_store.info +++ b/uc_store/uc_store.info @@ -11,6 +11,7 @@ files[] = classes/encrypt.inc ; Test cases files[] = tests/uc_store.test files[] = tests/uc_address.test +files[] = tests/uc_ajax.test configure = admin/store/settings/store stylesheets[all][] = uc_store.css -- 1.7.3.4 From c806d96e23686b7c8f95c3ec04f09c7c3425034a Mon Sep 17 00:00:00 2001 From: Chris Oden Date: Thu, 19 Apr 2012 13:08:43 -0500 Subject: [PATCH 17/17] Move some core default ajax behaviors into _uc_ajax_defaults() so they can be overridden by admins. --- payment/uc_payment/uc_payment_checkout_pane.inc | 4 ---- shipping/uc_quote/uc_quote.module | 3 --- uc_store/includes/uc_ajax_attach.inc | 14 ++++++++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/payment/uc_payment/uc_payment_checkout_pane.inc b/payment/uc_payment/uc_payment_checkout_pane.inc index f0aae20..68f7b00 100644 --- a/payment/uc_payment/uc_payment_checkout_pane.inc +++ b/payment/uc_payment/uc_payment_checkout_pane.inc @@ -92,10 +92,6 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL $contents['details']['#markup'] = t('Continue with checkout to complete payment.'); } - $ajax_attach = array('payment-pane' => 'uc_ajax_replace_checkout_pane'); - $form_state['uc_ajax']['uc_payment']['panes][billing][address][billing_country'] = $ajax_attach; - $form_state['uc_ajax']['uc_payment']['panes][delivery][address][delivery_country'] = $ajax_attach; - return array('description' => $description, 'contents' => $contents); case 'process': diff --git a/shipping/uc_quote/uc_quote.module b/shipping/uc_quote/uc_quote.module index 3fa49f1..63af571 100644 --- a/shipping/uc_quote/uc_quote.module +++ b/shipping/uc_quote/uc_quote.module @@ -583,9 +583,6 @@ function uc_checkout_pane_quotes($op, &$order, $form = NULL, &$form_state = NULL 'quotes-pane' => 'uc_ajax_replace_checkout_pane' ); $form_state['uc_ajax']['uc_quote']['panes][quotes][quote_button'] = $ajax_attach; - $form_state['uc_ajax']['uc_quote']['panes][delivery][address][delivery_country'] = $ajax_attach; - $form_state['uc_ajax']['uc_quote']['panes][delivery][address][delivery_postal_code'] = $ajax_attach; - $form_state['uc_ajax']['uc_quote']['panes][delivery][select_address'] = $ajax_attach; $form_state['uc_ajax']['uc_quote']['panes][quotes][quotes][quote_option'] = array('payment-pane' => 'uc_ajax_replace_checkout_pane'); return array('description' => $description, 'contents' => $contents); diff --git a/uc_store/includes/uc_ajax_attach.inc b/uc_store/includes/uc_ajax_attach.inc index fb7a248..979e311 100644 --- a/uc_store/includes/uc_ajax_attach.inc +++ b/uc_store/includes/uc_ajax_attach.inc @@ -24,7 +24,7 @@ * * @code * $form_state['uc_ajax']['mymodule']['panes][quotes][quote_button'] = array( - * 'quotes-pane' => 'uc_ajax_replace_checkout_pane', + * 'quotes-pane' => 'uc_ajax_replace_checkout_pane', * ); * @endcode * @@ -60,8 +60,8 @@ * * @code * $form_state['uc_ajax']['mymodule']['panes][quotes][quote_button'] = array( - * 'my_ajax_callback', - * 'quotes-pane' => 'uc_ajax_replace_checkout_pane', + * 'my_ajax_callback', + * 'quotes-pane' => 'uc_ajax_replace_checkout_pane', * ); * @endcode */ @@ -218,7 +218,13 @@ function uc_ajax_replace_checkout_pane($form, $form_state, $wrapper = NULL) { function _uc_ajax_defaults($target_form) { switch ($target_form) { case 'checkout': - return array(); + $quotes_defaults = drupal_map_assoc(array('payment-pane', 'quotes-pane')); + return array( + 'panes][delivery][address][delivery_country' => $quotes_defaults, + 'panes][delivery][address][delivery_postal_code' => $quotes_defaults, + 'panes][delivery][select_address' => $quotes_defaults, + 'panes][billing][address][billing_country' => array('payment-pane' => 'payment-pane'), + ); default: return array(); } -- 1.7.3.4