diff --git a/includes/ajax.inc b/includes/ajax.inc
index 8aa7471e520bab8658c9253fb8096833c0bde7ef..8cbf96a9bd73d8cae8b41fee621b1819e8795d73 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -24,20 +24,19 @@
  * a different callback function to invoke, which can return updated HTML or can
  * also return a richer set of AJAX framework commands.
  *
- * @see @link ajax_commands AJAX framework commands @endlink
+ * See @link ajax_commands AJAX framework commands @endlink
  *
  * To implement AJAX handling in a normal form, just add '#ajax' to the form
  * definition of a field. That field will trigger an AJAX event when it is
  * clicked (or changed, depending on the kind of field). #ajax supports
  * the following parameters (either 'path' or 'callback' is required at least):
  * - #ajax['path']: The menu path to use for the request. This path should map
- *   to a menu page callback that returns data using ajax_render(). By default,
- *   this is 'system/ajax'. Be warned that the default path currently only works
- *   for buttons. It will not work for selects, textfields, or textareas.
- * - #ajax['callback']: The callback to invoke, which will receive a $form and
- *   $form_state as arguments, and should return the HTML to replace. By
- *   default, the page callback defined for the menu path 'system/ajax' is
- *   triggered to handle the server side of the #ajax event.
+ *   to a menu page callback that returns data using ajax_render(). Defaults to
+ *   'system/ajax', which invokes ajax_form_callback().
+ * - #ajax['callback']: The callback to invoke to handle the server side of the
+ *   AJAX event, which will receive a $form and $form_state as arguments, and
+ *   should return a HTML string to replace the original element or a list of
+ *   AJAX commands.
  * - #ajax['wrapper']: The CSS ID of the AJAX area. The HTML returned from the
  *   callback will replace whatever is currently in this wrapper. It is
  *   important to ensure that this wrapper exists in the form. The wrapper is
@@ -74,8 +73,6 @@
  * be converted to a JSON object and returned to the client, which will then
  * iterate over the array and process it like a macro language.
  *
- * @see @link ajax_commands AJAX framework commands @endlink
- *
  * Each command is an object. $object->command is the type of command and will
  * be used to find the method (it will correlate directly to a method in
  * the Drupal.ajax[command] space). The object may contain any other data that
@@ -83,7 +80,6 @@
  *
  * Commands are usually created with a couple of helper functions, so they
  * look like this:
- *
  * @code
  *   $commands = array();
  *   // Replace the content of '#object-1' on the page with 'some html here'.
@@ -91,8 +87,29 @@
  *   // Add a visual "changed" marker to the '#object-1' element.
  *   $commands[] = ajax_command_changed('#object-1');
  *   // Output new markup to the browser and end the request.
+ *   // Note: Only custom AJAX paths/page callbacks need to do this manually.
  *   ajax_render($commands);
  * @endcode
+ *
+ * When the system's default #ajax['path'] is used, the invoked callback
+ * function can either return a HTML string or an AJAX command structure.
+ *
+ * In case an AJAX callback returns a HTML string instead of an AJAX command
+ * structure, ajax_form_callback() automatically replaces the original container
+ * by using the ajax_command_replace() command and additionally prepends the
+ * returned output with any status messages.
+ *
+ * When returning an AJAX command structure, it is likely that any status
+ * messages shall be output with the given HTML. To achieve the same result
+ * using an AJAX command structure, the AJAX callback may use the following:
+ * @code
+ *   $commands = array();
+ *   $commands[] = ajax_command_replace(NULL, $output);
+ *   $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
+ *   return $commands;
+ * @endcode
+ *
+ * See @link ajax_commands AJAX framework commands @endlink
  */
 
 /**
@@ -196,9 +213,37 @@ function ajax_get_form() {
 }
 
 /**
- * Menu callback for AJAX callbacks through the #ajax['callback'] Form API property.
+ * Menu callback; handles AJAX requests for the #ajax Form API property.
+ *
+ * This rebuilds the form from cache and invokes the defined #ajax['callback']
+ * to return an AJAX command structure for JavaScript. In case no 'callback' has
+ * been defined, nothing will happen.
+ *
+ * The Form API #ajax property can be set both for buttons and other input
+ * elements.
+ *
+ * ajax_process_form() defines an additional 'formPath' JavaScript setting
+ * that is used by Drupal.ajax.prototype.beforeSubmit() to automatically inject
+ * an additional field 'ajax_triggering_element' to the submitted form values,
+ * which contains the array #parents of the element in the form structure.
+ * This additional field allows ajax_form_callback() to determine which
+ * element triggered the action, as non-submit form elements do not
+ * provide this information in $form_state['clicked_button'], which can
+ * also be used to determine triggering element, but only submit-type
+ * form elements.
+ *
+ * This function is also the canonical example of how to implement
+ * #ajax['path']. If processing is required that cannot be accomplished with
+ * a callback, re-implement this function and set #ajax['path'] to the
+ * enhanced function.
  */
 function ajax_form_callback() {
+  // Find the triggering element, which was set up for us on the client side.
+  if (!empty($_REQUEST['ajax_triggering_element'])) {
+    $triggering_element_path = $_REQUEST['ajax_triggering_element'];
+    // Remove the value for form validation.
+    unset($_REQUEST['ajax_triggering_element']);
+  }
   list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
 
   // Build, validate and if possible, submit the form.
@@ -208,17 +253,40 @@ function ajax_form_callback() {
   // drupal_process_form() set up.
   $form = drupal_rebuild_form($form_id, $form_state, $form_build_id);
 
-  // Get the callback function from the clicked button.
-  $ajax = $form_state['clicked_button']['#ajax'];
-  $callback = $ajax['callback'];
-  if (function_exists($callback)) {
+  // $triggering_element_path in a simple form might just be 'myselect', which
+  // would mean we should use the element $form['myselect']. For nested form
+  // elements we need to recurse into the form structure to find the triggering
+  // element, so we can retrieve the #ajax['callback'] from it.
+  if (!empty($triggering_element_path)) {
+    if (!isset($form['#access']) || $form['#access']) {
+      $triggering_element = $form;
+      foreach (explode('/', $triggering_element_path) as $key) {
+        if (!empty($triggering_element[$key]) && (!isset($triggering_element[$key]['#access']) || $triggering_element[$key]['#access'])) {
+          $triggering_element = $triggering_element[$key];
+        }
+        else {
+          // We did not find the $triggering_element or do not have #access,
+          // so break out and do not provide it.
+          $triggering_element = NULL;
+          break;
+        }
+      }
+    }
+  }
+  if (empty($triggering_element)) {
+    $triggering_element = $form_state['clicked_button'];
+  }
+  // Now that we have the element, get a callback if there is one.
+  if (!empty($triggering_element)) {
+    $callback = $triggering_element['#ajax']['callback'];
+  }
+  if (!empty($callback) && function_exists($callback)) {
     $html = $callback($form, $form_state);
 
     // If the returned value is a string, assume it is HTML, add the status
     // messages, and create a command object to return automatically. We want
     // the status messages inside the new wrapper, so that they get replaced
     // on subsequent AJAX calls for the same wrapper.
-    // @see ajax_command_replace()
     if (is_string($html)) {
       $commands = array();
       $commands[] = ajax_command_replace(NULL, $html);
@@ -311,6 +379,7 @@ function ajax_process_form($element) {
       'method'   => empty($element['#ajax']['method']) ? 'replace' : $element['#ajax']['method'],
       'progress' => empty($element['#ajax']['progress']) ? array('type' => 'throbber') : $element['#ajax']['progress'],
       'button'   => isset($element['#executes_submit_callback']) ? array($element['#name'] => $element['#value']) : FALSE,
+      'formPath' => implode('/', $element['#array_parents']),
     );
 
     // Convert a simple #ajax['progress'] type string into an array.
@@ -341,7 +410,6 @@ function ajax_process_form($element) {
 /**
  * @defgroup ajax_commands AJAX framework commands
  * @{
- * @ingroup ajax
  */
 
 /**
@@ -376,19 +444,6 @@ function ajax_command_alert($text) {
  * This command is implemented by Drupal.ajax.prototype.commands.insert()
  * defined in misc/ajax.js.
  *
- * When using this command, it is likely that any status messages shall be
- * output with the given HTML. In case an AJAX callback returns a HTML string
- * instead of an AJAX command structure, ajax_form_callback() automatically
- * prepends the returned output with status messages.
- * To achieve the same result using an AJAX command structure, the AJAX callback
- * may use the following:
- * @code
- *   $commands = array();
- *   $commands[] = ajax_command_replace(NULL, $output);
- *   $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
- *   return $commands;
- * @endcode
- *
  * @param $selector
  *   A jQuery selector string. If the command is a response to a request from
  *   an #ajax form element then this value can be NULL.
diff --git a/misc/ajax.js b/misc/ajax.js
index 378b1b8336f55186a67647663bc0f99d33b9b6b7..79217dfd614ffecb3915187fd407e9c067467d70 100644
--- a/misc/ajax.js
+++ b/misc/ajax.js
@@ -183,6 +183,10 @@ Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
   // Disable the element that received the change.
   $(this.element).addClass('progress-disabled').attr('disabled', true);
 
+  // Server-side code needs to know what element triggered the call, so it can
+  // find the #ajax binding.
+  form_values.push({ name: 'ajax_triggering_element', value: this.formPath });
+
   // 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));