|
| 1 | +<?php |
| 2 | + |
| 3 | +/** |
| 4 | + * @file |
| 5 | + * Demonstrates using Drupal's Render API. |
| 6 | + */ |
| 7 | + |
| 8 | +/** |
| 9 | + * @defgroup render_example Example: Render API |
| 10 | + * @ingroup examples |
| 11 | + * @{ |
| 12 | + * The @link https://www.drupal.org/docs/8/api/render-api Render API @endlink |
| 13 | + * consists of two parts: one, structured arrays that provide data, and hints |
| 14 | + * about how that data should be rendered, and two, a rendering pipeline that |
| 15 | + * can be used to render these arrays into various output formats. This example |
| 16 | + * module looks at how to define content using render arrays, as well as how to |
| 17 | + * use alter hooks to manipulate render arrays created by other modules. |
| 18 | + * |
| 19 | + * For more on the rendering pipeline see @link |
| 20 | + * https://www.drupal.org/docs/8/api/render-api/the-drupal-8-render-pipeline The |
| 21 | + * Drupal 8 Render Pipeline @endlink. |
| 22 | + * |
| 23 | + * In order to ensure that a theme can completely customize the markup output |
| 24 | + * by Drupal, module developers should avoid directly writing HTML markup for |
| 25 | + * pages, blocks, and other user-visible output in their modules, and should |
| 26 | + * instead return structured "render arrays". Checkout the example code in |
| 27 | + * \Drupal\render_example\Controller\RenderExampleController::arrays() for an |
| 28 | + * explanation of how to define new renderable arrays. The output from that |
| 29 | + * code can be viewed at examples/render_example/arrays. |
| 30 | + * |
| 31 | + * One of the primary benefits of using arrays to define content instead of |
| 32 | + * strings of HTML is that arrays are easier to manipulate. There are dozens of |
| 33 | + * hooks, and other ways to gain access to and manipulate existing render arrays |
| 34 | + * during the rendering process both from within a module, and via a theme. As |
| 35 | + * a rule of thumb the process of rendering an array into HTML is delayed for as |
| 36 | + * long as possible. In most cases it's not until the variable containing the |
| 37 | + * content to be rendered is printed out in a Twig template file that it is |
| 38 | + * finally rendered. |
| 39 | + * |
| 40 | + * For examples of altering render arrays checkout the code in |
| 41 | + * render_example_preprocess_page() and render_example_preprocess_block(). There |
| 42 | + * is a form at examples/render_example/altering that can be used to turn these |
| 43 | + * features on and off if you would like to see the results of the array |
| 44 | + * altering code on your site. |
| 45 | + * |
| 46 | + * This module contains code that can display the render array used to build |
| 47 | + * each page, and/or block, as you navigate through a site as a way to show some |
| 48 | + * examples of real render arrays being used. This functionality requires that |
| 49 | + * the @link https://www.drupal.org/project/devel Devel module @endlink be |
| 50 | + * installed in order to work. |
| 51 | + * |
| 52 | + * Modules can also provide new render element types. A powerful way to |
| 53 | + * encapsulate complex display logic into a reusable widget. This can help to |
| 54 | + * cut down on code repetition, and allow other module developers to build off |
| 55 | + * of your work. See an example of a new render element definition by looking at |
| 56 | + * \Drupal\render_example\Element\Marquee. |
| 57 | + * |
| 58 | + * Forms are generated using a superset of the Render API. You can see examples |
| 59 | + * of how the Render API is used when creating forms in the fapi_example module. |
| 60 | + * |
| 61 | + * @see theme_render |
| 62 | + * @see \Drupal\Core\Render\RendererInterface::render() |
| 63 | + * @see \Drupal\Core\Template\TwigExtension::renderVar() |
| 64 | + */ |
| 65 | + |
| 66 | +use Drupal\Core\Render\Element; |
| 67 | + |
| 68 | +/** |
| 69 | + * Implements hook_theme(). |
| 70 | + */ |
| 71 | +function render_example_theme() { |
| 72 | + return [ |
| 73 | + // These theme hooks are both used by examples in |
| 74 | + // \Drupal\render_example\Controller\RenderExampleController::arrays(). |
| 75 | + 'render_example_add_div' => ['render element' => 'element'], |
| 76 | + 'render_array' => ['render element' => 'element'], |
| 77 | + // This is used in combination with \Drupal\render_example\Element\Marquee |
| 78 | + // to define a new custom render element type that allows for the use of |
| 79 | + // '#type' => 'marquee' elements in a render array. |
| 80 | + 'render_example_marquee' => [ |
| 81 | + 'variables' => [ |
| 82 | + 'content' => '', |
| 83 | + 'attributes' => [], |
| 84 | + ], |
| 85 | + ], |
| 86 | + ]; |
| 87 | +} |
| 88 | + |
| 89 | +/** |
| 90 | + * Example '#post_render' callback function. |
| 91 | + * |
| 92 | + * Post render callbacks are triggered after an element has been rendered to |
| 93 | + * HTML and can act upon the final rendered string. |
| 94 | + * |
| 95 | + * This function is made use of by one of the examples in |
| 96 | + * Drupal\render_example\Controller\RenderExampleController::arrays(). |
| 97 | + * |
| 98 | + * @param string $markup |
| 99 | + * The rendered element. |
| 100 | + * @param array $element |
| 101 | + * The element which was rendered (for reference) |
| 102 | + * |
| 103 | + * @return string |
| 104 | + * Markup altered as necessary. In this case we add a little postscript to it. |
| 105 | + * |
| 106 | + * @see \Drupal\render_example\Controller\RenderExampleController::arrays() |
| 107 | + */ |
| 108 | +function render_example_add_prefix($markup, array $element) { |
| 109 | + $markup = $markup . '<div style="color:blue">This markup was added after rendering by a #post_render callback.</div>'; |
| 110 | + return $markup; |
| 111 | +} |
| 112 | + |
| 113 | +/** |
| 114 | + * Example '#pre_render' function. |
| 115 | + * |
| 116 | + * Pre render callbacks are triggered prior to rendering an element to HTML and |
| 117 | + * are given the chance to manipulate the renderable array. Any changes they |
| 118 | + * make will be reflected in the final rendered HTML. |
| 119 | + * |
| 120 | + * This function is made use of by one of the examples in |
| 121 | + * Drupal\render_example\Controller\RenderExampleController::arrays(). |
| 122 | + * |
| 123 | + * @param array $element |
| 124 | + * The element which will be rendered. |
| 125 | + * |
| 126 | + * @return array |
| 127 | + * The altered element. In this case we add a #prefix to it. |
| 128 | + * |
| 129 | + * @see \Drupal\render_example\Controller\RenderExampleController::arrays() |
| 130 | + */ |
| 131 | +function render_example_add_suffix(array $element) { |
| 132 | + $element['#suffix'] = '<div style="color:red">' . t('This #suffix was added by a #pre_render callback.') . '</div>'; |
| 133 | + return $element; |
| 134 | +} |
| 135 | + |
| 136 | +/** |
| 137 | + * Implements hook_preprocess_page(). |
| 138 | + * |
| 139 | + * Demonstrates using a preprocess function to alter the renderable array that |
| 140 | + * represents the page currently being viewed. |
| 141 | + */ |
| 142 | +function render_example_preprocess_page(&$variables) { |
| 143 | + // Only modify the 'altering' page. |
| 144 | + if (\Drupal::routeMatch()->getRouteName() != 'render_example.altering') { |
| 145 | + return; |
| 146 | + } |
| 147 | + |
| 148 | + $config = \Drupal::config('render_example.settings'); |
| 149 | + |
| 150 | + // Preprocess hooks are invoked by the theme layer, and are used to give |
| 151 | + // modules a chance to manipulate the variables that are going to be made |
| 152 | + // available to a specific template file. Since content is still defined as |
| 153 | + // renderable arrays at this point you can do quite a bit to manipulate the |
| 154 | + // eventual output by altering these arrays. |
| 155 | + // |
| 156 | + // The $page variable in this case contains the complete content of the page |
| 157 | + // including all regions, and the blocks placed within each region. |
| 158 | + // |
| 159 | + // The actual process of converting a renderable array to HTML is started when |
| 160 | + // this variable is printed out within a Twig template. Drupal's Twig |
| 161 | + // extension provides a wrapper around the Twig code that prints out variables |
| 162 | + // which checks to see if the variable being printed is a renderable array and |
| 163 | + // passes it through \Drupal\Core\Render\RendererInterface::render() before |
| 164 | + // printing it to the screen. |
| 165 | + $page = &$variables['page']; |
| 166 | + |
| 167 | + // Move the breadcrumbs into the content area. |
| 168 | + if ($config->get('move_breadcrumbs') && !empty($page['breadcrumb']) && !empty($page['content'])) { |
| 169 | + $page['content']['breadcrumb'] = $page['breadcrumb']; |
| 170 | + unset($page['breadcrumb']); |
| 171 | + $page['content']['breadcrumb']['#weight'] = -99999; |
| 172 | + |
| 173 | + // Force the content to be re-sorted. |
| 174 | + $page['content']['#sorted'] = FALSE; |
| 175 | + } |
| 176 | + |
| 177 | + // Re-sort the contents of the sidebar in reverse order. |
| 178 | + if ($config->get('reverse_sidebar') && !empty($page['sidebar_first'])) { |
| 179 | + $page['sidebar_first'] = array_reverse($page['sidebar_first']); |
| 180 | + foreach (Element::children($page['sidebar_first']) as $element) { |
| 181 | + // Reverse the weights if they exist. |
| 182 | + if (!empty($page['sidebar_first'][$element]['#weight'])) { |
| 183 | + $page['sidebar_first'][$element]['#weight'] *= -1; |
| 184 | + } |
| 185 | + } |
| 186 | + // This forces the sidebar to be re-sorted. |
| 187 | + $page['sidebar_first']['#sorted'] = FALSE; |
| 188 | + } |
| 189 | + |
| 190 | + // Show the render array used to build the current page. |
| 191 | + // This relies on the Devel module's variable dumper service. |
| 192 | + // https://wwww.drupal.org/project/devel |
| 193 | + if (Drupal::moduleHandler()->moduleExists('devel') && $config->get('show_page')) { |
| 194 | + $page['content']['page_render_array'] = [ |
| 195 | + '#type' => 'markup', |
| 196 | + '#prefix' => '<h2>' . t('The page render array') . '</h2>', |
| 197 | + // The devel.dumper service is provided by the devel module and makes for |
| 198 | + // and easier to read var_dump(). Especially if the companion Kint module |
| 199 | + // is enabled. |
| 200 | + 'dump' => \Drupal::service('devel.dumper')->exportAsRenderable($page, '$page'), |
| 201 | + '#weight' => -99999, |
| 202 | + ]; |
| 203 | + |
| 204 | + $page['content']['#sorted'] = FALSE; |
| 205 | + } |
| 206 | +} |
| 207 | + |
| 208 | +/** |
| 209 | + * Implements hook_preprocess_block(). |
| 210 | + */ |
| 211 | +function render_example_preprocess_block(&$variables) { |
| 212 | + // Only modify the 'altering' page. |
| 213 | + if (\Drupal::routeMatch()->getRouteName() != 'render_example.altering') { |
| 214 | + return; |
| 215 | + } |
| 216 | + |
| 217 | + $config = \Drupal::config('render_example.settings'); |
| 218 | + |
| 219 | + // This example shows how you can manipulate an existing renderable array. In |
| 220 | + // this case by adding #prefix and #suffix properties to the block in order to |
| 221 | + // wrap a <div> around it. |
| 222 | + if ($config->get('wrap_blocks')) { |
| 223 | + $variables['content']['#prefix'] = '<div class="block-prefix"><p>' . t('Prefixed') . '</p>'; |
| 224 | + $variables['content']['#suffix'] = '<span class="block-suffix">' . t('Block suffix') . '</span></div>'; |
| 225 | + } |
| 226 | + |
| 227 | + // Show the render array used to build each block if the Devel module is |
| 228 | + // installed and the feature is enabled. |
| 229 | + if (Drupal::moduleHandler()->moduleExists('devel') && $config->get('show_block')) { |
| 230 | + $variables['content']['block_render_array'] = [ |
| 231 | + '#type' => 'markup', |
| 232 | + '#prefix' => '<h2>' . t('The block render array for @block_id.', ['@block_id' => $variables['plugin_id']]) . '</h2>', |
| 233 | + 'dump' => \Drupal::service('devel.dumper')->exportAsRenderable($variables, $variables['plugin_id']), |
| 234 | + ]; |
| 235 | + } |
| 236 | +} |
| 237 | + |
| 238 | +/** |
| 239 | + * @} End of "defgroup render_example". |
| 240 | + */ |
0 commit comments