From cf280ae2b3ee326d9f83053e24fd370d7f74a663 Mon Sep 17 00:00:00 2001
From: Mark Carver <mark.carver@me.com>
Date: Thu, 23 Feb 2017 11:52:42 -0600
Subject: [PATCH] Issue #2855458 by markcarver: Action dropbuttons are
 prematurely rendered on only in #process

---
 core/lib/Drupal/Core/Render/Element/Actions.php | 44 ++++++++++++-------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/core/lib/Drupal/Core/Render/Element/Actions.php b/core/lib/Drupal/Core/Render/Element/Actions.php
index 1f94466..9b841ad 100644
--- a/core/lib/Drupal/Core/Render/Element/Actions.php
+++ b/core/lib/Drupal/Core/Render/Element/Actions.php
@@ -32,11 +32,12 @@ public function getInfo() {
     $class = get_class($this);
     return array(
       '#process' => array(
-        // @todo Move this to #pre_render.
-        array($class, 'preRenderActionsDropbutton'),
         array($class, 'processActions'),
         array($class, 'processContainer'),
       ),
+      '#pre_render' => array(
+        array($class, 'preRenderActionsDropbutton'),
+      ),
       '#weight' => 100,
       '#theme_wrappers' => array('container'),
     );
@@ -66,47 +67,46 @@ public static function processActions(&$element, FormStateInterface $form_state,
    *
    * This callback iterates over all child elements of the #type 'actions'
    * container to look for elements with a #dropbutton property, so as to group
-   * those elements into dropbuttons. As such, it works similar to #group, but is
-   * specialized for dropbuttons.
+   * those elements into dropbuttons. As such, it works similar to #group, but
+   * is specialized for dropbuttons.
    *
    * The value of #dropbutton denotes the dropbutton to group the child element
-   * into. For example, two different values of 'foo' and 'bar' on child elements
-   * would generate two separate dropbuttons, which each contain the corresponding
-   * buttons.
+   * into. For example, two different values of 'foo' and 'bar' on child
+   * elements would generate two separate dropbuttons, which each contain the
+   * corresponding buttons.
    *
    * @param array $element
-   *   The #type 'actions' element to process.
-   * @param \Drupal\Core\Form\FormStateInterface $form_state
-   *   The current state of the form.
-   * @param array $complete_form
-   *   The complete form structure.
+   *   The #type 'actions' element to pre-render.
    *
    * @return array
-   *   The processed #type 'actions' element, including individual buttons grouped
-   *   into new #type 'dropbutton' elements.
+   *   The pre-rendered #type 'actions' element, including individual buttons
+   *   grouped into new #type 'dropbutton' elements.
    */
-  public static function preRenderActionsDropbutton(&$element, FormStateInterface $form_state, &$complete_form) {
+  public static function preRenderActionsDropbutton($element) {
     $dropbuttons = array();
     foreach (Element::children($element, TRUE) as $key) {
       if (isset($element[$key]['#dropbutton'])) {
         $dropbutton = $element[$key]['#dropbutton'];
+
         // If there is no dropbutton for this button group yet, create one.
         if (!isset($dropbuttons[$dropbutton])) {
           $dropbuttons[$dropbutton] = array(
             '#type' => 'dropbutton',
           );
         }
-        // Add this button to the corresponding dropbutton.
-        // @todo Change #type 'dropbutton' to be based on item-list.html.twig
-        //   instead of links.html.twig to avoid this preemptive rendering.
-        $button = \Drupal::service('renderer')->renderPlain($element[$key]);
+
+        // Add button to the corresponding dropbutton by cloning it.
+        // Note: do not prematurely render this as it will break any #attached
+        // bubbled metadata that is associated with the button.
         $dropbuttons[$dropbutton]['#links'][$key] = array(
-          'title' => $button,
+          'title' => $element[$key],
         );
+
+        // Hide original cloned element by indicating it was already printed.
+        $element[$key]['#printed'] = TRUE;
       }
     }
-    // @todo For now, all dropbuttons appear first. Consider to invent a more
-    //   fancy sorting/injection algorithm here.
+    // Prepend dropbuttons with the original element.
     return $dropbuttons + $element;
   }
 
-- 
2.8.3

