diff --git a/composer.json b/composer.json
index 26d4063..f0b4f77 100644
--- a/composer.json
+++ b/composer.json
@@ -8,5 +8,8 @@
"documentation": "https://api.drupal.org/api/examples",
"source": "http://cgit.drupalcode.org/examples"
},
+ "suggest": {
+ "drupal/devel": "Some modules will be able to pretty-print PHP with this module."
+ },
"license": "GPL-2.0+"
}
diff --git a/examples.module b/examples.module
index 8f732da..3dd05da 100644
--- a/examples.module
+++ b/examples.module
@@ -55,6 +55,7 @@ function examples_toolbar() {
'pager_example' => 'pager_example.page',
'phpunit_example' => 'phpunit_example_description',
'plugin_type_example' => 'plugin_type_example.description',
+ 'render_example' => 'render_example.description',
'simpletest_example' => 'simpletest_example_description',
'stream_wrapper_example' => 'stream_wrapper_example.description',
'testing_example' => 'testing_example.description',
diff --git a/render_example/config/install/render_example.settings.yml b/render_example/config/install/render_example.settings.yml
new file mode 100644
index 0000000..f1df332
--- /dev/null
+++ b/render_example/config/install/render_example.settings.yml
@@ -0,0 +1,6 @@
+show_block: false
+show_page: false
+note_about_render_arrays: false
+move_breadcrumbs: false
+reverse_sidebar: false
+wrap_blocks: false
diff --git a/render_example/config/schema/render_example.schema.yml b/render_example/config/schema/render_example.schema.yml
new file mode 100644
index 0000000..ca8b19c
--- /dev/null
+++ b/render_example/config/schema/render_example.schema.yml
@@ -0,0 +1,16 @@
+render_example.settings:
+ type: config_object
+ label: 'Render Example Settings'
+ mapping:
+ show_block:
+ type: boolean
+ show_page:
+ type: boolean
+ note_about_render_arrays:
+ type: boolean
+ move_breadcrumbs:
+ type: boolean
+ reverse_sidebar:
+ type: boolean
+ wrap_blocks:
+ type: boolean
diff --git a/render_example/css/render_example.css b/render_example/css/render_example.css
new file mode 100644
index 0000000..3f79120
--- /dev/null
+++ b/render_example/css/render_example.css
@@ -0,0 +1,15 @@
+.render_example--array {
+ border-bottom: 2px solid #000;
+ margin-top: 10px;
+ padding-left: 5px;
+ padding-top: 5px;
+}
+
+.render_example--code {
+ background: #f1f1f1;
+ border: 1px solid #000;
+ display: block;
+ max-height: 300px;
+ overflow: scroll;
+ padding: 10px;
+}
diff --git a/render_example/render_example.info.yml b/render_example/render_example.info.yml
new file mode 100644
index 0000000..ddf0447
--- /dev/null
+++ b/render_example/render_example.info.yml
@@ -0,0 +1,10 @@
+name: Render example
+type: module
+description: Provides examples demonstrating Drupal's Render API.
+package: Example modules
+core: 8.x
+dependencies:
+ - core:block
+ - core:node
+ - core:user
+ - examples:examples
diff --git a/render_example/render_example.libraries.yml b/render_example/render_example.libraries.yml
new file mode 100644
index 0000000..d45506d
--- /dev/null
+++ b/render_example/render_example.libraries.yml
@@ -0,0 +1,9 @@
+# Define a new asset library which includes the CSS used to style the page
+# at examples/render_example/arrays. See how asset libraries are attached to
+# render arrays by reading the example code in
+# Drupal\render_example\Controller\RenderExampleController::arrays().
+render-example-css:
+ version: 1.x
+ css:
+ theme:
+ css/render_example.css: {}
diff --git a/render_example/render_example.links.menu.yml b/render_example/render_example.links.menu.yml
new file mode 100644
index 0000000..76afbc3
--- /dev/null
+++ b/render_example/render_example.links.menu.yml
@@ -0,0 +1,19 @@
+render_example.description:
+ title: Render Example
+ description: Examples of building and altering render arrays.
+ route_name: render_example.description
+ expanded: TRUE
+
+render_example.altering:
+ title: Altering Render Arrays
+ description: Using hooks and callbacks to alter render arrays.
+ route_name: render_example.altering
+ parent: render_example.description
+ weight: -9
+
+render_example.arrays:
+ title: Building Render Arrays
+ description: Building render arrays in controllers.
+ route_name: render_example.arrays
+ parent: render_example.description
+ weight: -8
diff --git a/render_example/render_example.module b/render_example/render_example.module
new file mode 100644
index 0000000..79524b4
--- /dev/null
+++ b/render_example/render_example.module
@@ -0,0 +1,240 @@
+ ['render element' => 'element'],
+ 'render_array' => ['render element' => 'element'],
+ // This is used in combination with \Drupal\render_example\Element\Marquee
+ // to define a new custom render element type that allows for the use of
+ // '#type' => 'marquee' elements in a render array.
+ 'render_example_marquee' => [
+ 'variables' => [
+ 'content' => '',
+ 'attributes' => [],
+ ],
+ ],
+ ];
+}
+
+/**
+ * Example '#post_render' callback function.
+ *
+ * Post render callbacks are triggered after an element has been rendered to
+ * HTML and can act upon the final rendered string.
+ *
+ * This function is made use of by one of the examples in
+ * Drupal\render_example\Controller\RenderExampleController::arrays().
+ *
+ * @param string $markup
+ * The rendered element.
+ * @param array $element
+ * The element which was rendered (for reference)
+ *
+ * @return string
+ * Markup altered as necessary. In this case we add a little postscript to it.
+ *
+ * @see \Drupal\render_example\Controller\RenderExampleController::arrays()
+ */
+function render_example_add_prefix($markup, array $element) {
+ $markup = $markup . '
This markup was added after rendering by a #post_render callback.
';
+ return $markup;
+}
+
+/**
+ * Example '#pre_render' function.
+ *
+ * Pre render callbacks are triggered prior to rendering an element to HTML and
+ * are given the chance to manipulate the renderable array. Any changes they
+ * make will be reflected in the final rendered HTML.
+ *
+ * This function is made use of by one of the examples in
+ * Drupal\render_example\Controller\RenderExampleController::arrays().
+ *
+ * @param array $element
+ * The element which will be rendered.
+ *
+ * @return array
+ * The altered element. In this case we add a #prefix to it.
+ *
+ * @see \Drupal\render_example\Controller\RenderExampleController::arrays()
+ */
+function render_example_add_suffix(array $element) {
+ $element['#suffix'] = '' . t('This #suffix was added by a #pre_render callback.') . '
';
+ return $element;
+}
+
+/**
+ * Implements hook_preprocess_page().
+ *
+ * Demonstrates using a preprocess function to alter the renderable array that
+ * represents the page currently being viewed.
+ */
+function render_example_preprocess_page(&$variables) {
+ // Only modify the 'altering' page.
+ if (\Drupal::routeMatch()->getRouteName() != 'render_example.altering') {
+ return;
+ }
+
+ $config = \Drupal::config('render_example.settings');
+
+ // Preprocess hooks are invoked by the theme layer, and are used to give
+ // modules a chance to manipulate the variables that are going to be made
+ // available to a specific template file. Since content is still defined as
+ // renderable arrays at this point you can do quite a bit to manipulate the
+ // eventual output by altering these arrays.
+ //
+ // The $page variable in this case contains the complete content of the page
+ // including all regions, and the blocks placed within each region.
+ //
+ // The actual process of converting a renderable array to HTML is started when
+ // this variable is printed out within a Twig template. Drupal's Twig
+ // extension provides a wrapper around the Twig code that prints out variables
+ // which checks to see if the variable being printed is a renderable array and
+ // passes it through \Drupal\Core\Render\RendererInterface::render() before
+ // printing it to the screen.
+ $page = &$variables['page'];
+
+ // Move the breadcrumbs into the content area.
+ if ($config->get('move_breadcrumbs') && !empty($page['breadcrumb']) && !empty($page['content'])) {
+ $page['content']['breadcrumb'] = $page['breadcrumb'];
+ unset($page['breadcrumb']);
+ $page['content']['breadcrumb']['#weight'] = -99999;
+
+ // Force the content to be re-sorted.
+ $page['content']['#sorted'] = FALSE;
+ }
+
+ // Re-sort the contents of the sidebar in reverse order.
+ if ($config->get('reverse_sidebar') && !empty($page['sidebar_first'])) {
+ $page['sidebar_first'] = array_reverse($page['sidebar_first']);
+ foreach (Element::children($page['sidebar_first']) as $element) {
+ // Reverse the weights if they exist.
+ if (!empty($page['sidebar_first'][$element]['#weight'])) {
+ $page['sidebar_first'][$element]['#weight'] *= -1;
+ }
+ }
+ // This forces the sidebar to be re-sorted.
+ $page['sidebar_first']['#sorted'] = FALSE;
+ }
+
+ // Show the render array used to build the current page.
+ // This relies on the Devel module's variable dumper service.
+ // https://wwww.drupal.org/project/devel
+ if (Drupal::moduleHandler()->moduleExists('devel') && $config->get('show_page')) {
+ $page['content']['page_render_array'] = [
+ '#type' => 'markup',
+ '#prefix' => ' around it.
+ if ($config->get('wrap_blocks')) {
+ $variables['content']['#prefix'] = '
' . t('Prefixed') . '
';
+ $variables['content']['#suffix'] = '
' . t('Block suffix') . '';
+ }
+
+ // Show the render array used to build each block if the Devel module is
+ // installed and the feature is enabled.
+ if (Drupal::moduleHandler()->moduleExists('devel') && $config->get('show_block')) {
+ $variables['content']['block_render_array'] = [
+ '#type' => 'markup',
+ '#prefix' => '
' . t('The block render array for @block_id.', ['@block_id' => $variables['plugin_id']]) . '
',
+ 'dump' => \Drupal::service('devel.dumper')->exportAsRenderable($variables, $variables['plugin_id']),
+ ];
+ }
+}
+
+/**
+ * @} End of "defgroup render_example".
+ */
diff --git a/render_example/render_example.routing.yml b/render_example/render_example.routing.yml
new file mode 100644
index 0000000..5f93efa
--- /dev/null
+++ b/render_example/render_example.routing.yml
@@ -0,0 +1,21 @@
+render_example.description:
+ path: 'examples/render-example'
+ defaults:
+ _controller: '\Drupal\render_example\Controller\RenderExampleController::description'
+ requirements:
+ _permission: 'access content'
+
+render_example.altering:
+ path: 'examples/render-example/altering'
+ defaults:
+ _form: '\Drupal\render_example\Form\RenderExampleDemoForm'
+ _title: 'Alter pages and blocks'
+ requirements:
+ _permission: 'access content'
+
+render_example.arrays:
+ path: 'examples/render-example/arrays'
+ defaults:
+ _controller: '\Drupal\render_example\Controller\RenderExampleController::arrays'
+ requirements:
+ _permission: 'access content'
diff --git a/render_example/src/Controller/RenderExampleController.php b/render_example/src/Controller/RenderExampleController.php
new file mode 100644
index 0000000..61ea14e
--- /dev/null
+++ b/render_example/src/Controller/RenderExampleController.php
@@ -0,0 +1,471 @@
+currentUser = $current_user;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('current_user')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getModuleName() {
+ return 'render_example';
+ }
+
+ /**
+ * Examples of defining content using renderable arrays.
+ *
+ * Methods on a controller that are the target of a route should return a
+ * renderable array which contains any content to display for that route.
+ */
+ public function arrays() {
+ // The core structure of the Render API is the render array, which is a
+ // hierarchical associative array containing data to be rendered and
+ // properties describing how the data should be rendered. Whenever a module
+ // needs to output content it should do so be defining that content as a
+ // renderable array. Below we'll look at some common examples of how render
+ // arrays can be used to define content.
+ $build = [];
+
+ // Renderable arrays have two kinds of key/value pairs: properties and
+ // children. Properties have keys starting with '#' and their values
+ // influence how the array will be translated to a string. Children are all
+ // elements whose keys do not start with a '#'. Their values should be
+ // renderable arrays themselves.
+ //
+ // This example defines a new element, 'simple', that contains two
+ // properties; '#markup' and '#description'. This is the quickest way to
+ // output a string of HTML.
+ $build['simple'] = [
+ '#markup' => '
' . $this->t('This page contains examples of various content elements described using render arrays. Read the code and comments in \Drupal\render_example\Controller\RenderExampleController::arrays() for more information.') . '
',
+ '#description' => $this->t('Example of using #markup'),
+ ];
+
+ // Additional properties can be used to further define the content. In this
+ // case '#prefix' and '#suffix' are being used to provide strings to add
+ // before, and after, the main content. This is useful because now the tag
+ // being used to wrap the block of content can be easily changed without
+ // having to worry about the content. Or, the tag can easily be left out
+ // during rendering if for example the content is being output as JSON.
+ //
+ // There is a set of common properties that can be used for all elements in
+ // a render array. These are defined by
+ // \Drupal\Core\Render\Element\RenderElement. Most elements also have
+ // additional element type specific properties.
+ //
+ // Figuring out what additional properties are available requires first
+ // determining what sort of render element you're dealing with. Look for the
+ // presence of one of these properties to start:
+ // - #markup, or #plain_text: These are the simplest render arrays, and are
+ // used to display simple strings of text. In addition to the common set
+ // of properties available for all elements #markup elements can use the
+ // #allowed_tags property, an array of additional tags to allow when the
+ // HTML string is run through \Drupal\Component\Utility\Xss::filterAdmin()
+ // to strip out possible XSS vectors.
+ // - #theme: The presence of #theme indicates that the array contains data
+ // to be themed by a particular theme hook. The available properties will
+ // depend on the specific theme hook. See the example below for more about
+ // determining what properties to use.
+ // - #type: The presence of #type indicates that the array contains data and
+ // options for a particular type of "render element" (for example, 'form',
+ // 'textfield', 'submit', for HTML form element types; 'table', for a
+ // table with rows, columns, and headers). The additional properties will
+ // depend on the render element type, and are documented on the class that
+ // defines the element type.
+ //
+ $build['simple_extras'] = [
+ '#description' => $this->t('Example of using #prefix and #suffix'),
+ // Note the addition of '#type' => 'markup' in this example compared to
+ // the one above. Because #markup is such a commonly used element type you
+ // can exclude the '#type' => 'markup' line and it will be assumed
+ // automatically if the '#markup' property is present.
+ '#type' => 'markup',
+ '#markup' => '
' . $this->t('This one adds a prefix and suffix, which put a blockqoute tag around the item.') . '
',
+ '#prefix' => '
',
+ '#suffix' => '
',
+ ];
+
+ // In addition to #markup, you can also use #plain_text to output, you
+ // guessed it, strings of plain text. This indicates that the array contains
+ // text which should be escaped before it is displayed.
+ $build['simple_text'] = [
+ '#plain_text' => '
This is escaped',
+ '#description' => $this->t('Example of using #plain_text'),
+ ];
+
+ // Using the '#theme' property for an element specifies that the array
+ // contains data to be themed by a particular theme hook. Essentially using
+ // a Twig template to generate the HTML for an element. Modules define theme
+ // hooks by implementing hook_theme(), which specifies the input "variables"
+ // used to provide data and options; if a hook_theme() implementation
+ // specifies variable 'separator', then in a render array, you would provide
+ // this data using the '#separator' property.
+ //
+ // @see hook_theme()
+ $build['theme_element'] = [
+ // The '#theme' property can be set to any valid theme hook. For more
+ // information about theme hooks, and to discover available theme hooks
+ // that you can use when creating render arrays see the documentation for
+ // hook_theme().
+ //
+ // Many of the most commonly used theme hooks are defined in
+ // drupal_common_theme().
+ '#theme' => 'item_list',
+ '#title' => $this->t('Example of using #theme'),
+ // The #items property is specific to the 'item_list' theme hook, and
+ // corresponds to the variable {{ items }} in the item-list.twig.html
+ // template file.
+ '#items' => [
+ 'value' => $this->t('This is some more text that we need in the list'),
+ 'attributes' => ['class' => ['item_list__item']],
+ ],
+ ];
+
+ // Using the '#type' property for an element specifies that the array
+ // contains data and options for a particular type of "render element".
+ // Render element types can be thought of as prepackaged render arrays that
+ // provide default values for a set of properties as well as code that
+ // will perform additional processing on the array before it is rendered.
+ //
+ // As an example take a look at the code in
+ // \Drupal\Core\Render\Element\Table::getInfo(). Notice that it is defining
+ // values for #theme, and #process? These values will be merged with
+ // whatever properties you define in your code.
+ //
+ // In addition, most render element types have type specific properties. A
+ // table for example has #header, and #rows properties. The easiest way to
+ // determine what element type specific properties exist is to read the
+ // documentation for the class that defines the element type. Don't forget
+ // that it will also inherit properties used by any class it is extending.
+ //
+ // There are two types of render element types:
+ // - Generic elements: Generic render element types encapsulate logic for
+ // generating HTML and attaching relevant CSS and JavaScript to the page.
+ // These include things like link, table, and drop button elements.
+ // - Form elements: Most of the render element types provided by core
+ // represent the various widgets you might use on a form. Text fields,
+ // password fields, and file upload buttons for example. These elements
+ // are intended to be used in conjunction with a form controller class
+ // and have additional properties such as `#required`, and
+ // `#element_validate`, related to their use as part of a form. For more
+ // on form elements check out the fapi_example module.
+ $build['table'] = [
+ // The value used for #type is the ID of the plugin that implements the
+ // element type you want to use. This can be inferred from the annotation
+ // for the element.
+ // You can also find a list of element types provided by Drupal core here
+ // https://api.drupal.org/api/drupal/elements.
+ '#type' => 'table',
+ '#caption' => $this->t('Our favorite colors.'),
+ '#header' => [$this->t('Name'), $this->t('Favorite color')],
+ '#rows' => [
+ [$this->t('Amber'), $this->t('teal')],
+ [$this->t('Addi'), $this->t('green')],
+ [$this->t('Blake'), $this->t('#063')],
+ [$this->t('Enid'), $this->t('indigo')],
+ [$this->t('Joe'), $this->t('green')],
+ ],
+ '#description' => $this->t('Example of using #type.'),
+ ];
+
+ // Render arrays can be nested any level deep. This allows you to group
+ // like things together. A great example of this is the $page array used in
+ // conjunction with the page.html.twig template. The top level contains all
+ // the regions, each of which contain the blocks placed in that region,
+ // which in turn contain their own content. In fact, when this array is
+ // ultimately displayed on a page it will be as part of the $page array.
+ $build['nested_example'] = [
+ '#description' => $this->t('Example of nesting elements'),
+ '#markup' => '
' . $this->t('Render arrays can contain any number of nested elements. During rendering, the innermost elements are rendered first, and their output is incorporated into the parent element.') . '
',
+ 'nested_child_element' => [
+ // An un-ordered list of links.
+ // See /core/modules/system/templates/item-list.html.twig.
+ '#theme' => 'item_list',
+ '#title' => $this->t('Links'),
+ '#list_type' => 'ol',
+ '#items' => [
+ Link::fromTextAndUrl($this->t('Drupal'), Url::fromUri('https://www.drupal.org')),
+ Link::fromTextAndUrl($this->t('Not Drupal'), Url::fromUri('https://wordpress.org/')),
+ ],
+ ],
+ ];
+
+ // Example of adding a link using the #link element type.
+ $build['nested_example']['another_nested_child'] = [
+ // See \Drupal\Core\Render\Element\Link.
+ '#type' => 'link',
+ '#title' => $this->t('A link to example.com'),
+ '#url' => Url::fromUri('https://example.com'),
+ ];
+
+ // The #theme_wrappers property can be used to provide an array of theme
+ // hooks which provide the envelope or "wrapper" of a set of child elements.
+ // The theme function finds its element children (the sub-arrays) already
+ // rendered in '#children'.
+ $build['theme_wrappers demonstration'] = [
+ '#description' => $this->t('Example of using #theme_wrappers'),
+ 'child1' => ['#markup' => $this->t('Markup for child1')],
+ 'child2' => ['#markup' => $this->t('Markup for child2')],
+ '#theme_wrappers' => [
+ 'render_example_add_div',
+ ],
+ ];
+
+ // Use the #access property to control who can see what content. If an
+ // element in an render array has its #access property set to FALSE it will
+ // be removed from the array before rendering. And thus not visible.
+ $build['access_example'] = [
+ '#description' => $this->t('Example of using #access to control visibility'),
+ '#markup' => $this->t('This text is only visible to authenticated users.'),
+ '#access' => $this->currentUser->isAuthenticated(),
+ ];
+
+ // Some properties define callbacks, which are callable functions or methods
+ // that are triggered at specific points during the rendering pipeline.
+ $build['pre_render and post_render'] = [
+ '#description' => $this->t('Example of using #pre_render and #post_render'),
+ '#markup' => '
' . t('markup for pre_render and post_render example') . '
',
+ // #pre_render callbacks are triggered early in the rendering process,
+ // they get access to the element in the array where the callback is
+ // named, and all of its children. They can be used to do things like
+ // conditionally alter the value of a property prior to the array being
+ // rendered to HTML.
+ '#pre_render' => ['render_example_add_suffix'],
+ // #post_render callbacks are triggered after the array has been rendered
+ // and can operate on the rendered HTML. They also have access to the
+ // original array for context.
+ '#post_render' => ['render_example_add_prefix'],
+ ];
+
+ // Properties that contain callbacks can also reference methods on a class
+ // in addition to functions. See
+ // \Drupal\render_example\Controller\RenderExampleController::preRender()
+ $build['#pre_render'] = [static::class . '::preRender'];
+
+ // Caching is an important part of the Render API, converting an array to a
+ // string of HTML can be an expensive process, and therefore whenever
+ // possible the Render API will cache the results of rendering an array in
+ // order to improve performance.
+ //
+ // When defining a render array you should use the #cache property to define
+ // the cachability of an element.
+ $build['cache_demonstration'] = [
+ '#description' => $this->t('#cache demonstration'),
+ // This string contains information that is specific to the user who is
+ // currently viewing the page. We can cache it, and re-use the string any
+ // time the same user views the page again. However, if the user changes,
+ // or if the user changes their name, we need to expire the cached data
+ // and rebuild it so that it is accurate.
+ '#markup' => $this->t('Hello @name, welcome to the #cache example.', ['@name' => $this->currentUser->getAccountName()]),
+ // The #cache property is used to provide metadata about the element being
+ // cached, and the conditions under which it should be expired. This can
+ // be time based, or context based. You can read more about caching
+ // render arrays here
+ // https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays
+ '#cache' => [
+ // The "current user" is used above, which depends on the request, so
+ // we tell Drupal to vary by the 'user' cache context.
+ 'contexts' => [
+ 'user',
+ ],
+ ],
+ ];
+
+ // A #lazy_builder callback can be used to build a highly dynamic section of
+ // a render array from scratch. This, combined with the use of placeholders,
+ // allows the renderer to cache some, but not all, portions of a render
+ // array. Without #lazy_builders, if any element in the render tree is
+ // uncacheable the whole tree would need to be re-rendered every time.
+ //
+ // The general rendering flow is as follows:
+ // - Check for cached version of output from previous rendering, if it
+ // exists replace any placeholders in the rendered output with their
+ // dynamic content as generated by the #lazy_builder callback, and return
+ // the resulting HTML.
+ // - If no cached version exists render the array to HTML, when an element
+ // that can be placeholdered is encountered insert a placeholder, cache
+ // the HTML after rendering for next time, replace the placeholders with
+ // their dynamic content, and return the resulting HTML.
+ //
+ // This is especially noticeable when used in conjunction with modules like
+ // Big Pipe which do rendering of a page in multiple passes vs. the default
+ // single flush renderer.
+ //
+ // See \Drupal\block\BlockViewBuilder::viewMultiple() for an example from
+ // core.
+ $build['lazy_builder'] = [
+ // Set the value of the #lazy_builder property to an array, the first key
+ // of the array is the method, service, or function, to call in oder to
+ // generate the dynamic data. The second argument is an array of any
+ // arguments to pass to the callback. Arguments can be only primitive
+ // types (string, bool, int, float, NULL).
+ '#lazy_builder' => [
+ static::class . '::lazyBuilder',
+ [$this->currentUser->id(), 'Y-m-d'],
+ ],
+ // #lazy_builder callbacks can be used in conjunction with
+ // #create_placeholder to tell the renderer that instead of simply calling
+ // the #lazy_builder code right away, to instead insert a placeholder and
+ // delay execution of the #lazy_builder code until it's needed.
+ //
+ // This is somewhat analogous to the way Drupal uses the PSR-4 autoloading
+ // standard to "lazy" load PHP files that contain the definition of a
+ // class only if, and when, that class is used.
+ //
+ // To force a element to use a placeholder set #create_placeholder to
+ // TRUE.
+ //
+ // Alternatively you could include #cache metadata (see above) and allow
+ // the Render API to use that metadata to automatically determine based on
+ // the existence of high-cardinality cache contexts in the subtree whether
+ // or not the element should use a placeholder.
+ '#create_placeholder' => TRUE,
+ ];
+
+ // CSS and JavaScript libraries can be attached to elements in a renderable
+ // array. This way, if the element ends up being rendered and displayed you
+ // know for sure the CSS/JavaScript will also be included. But, if the for
+ // some reason the element isn't ever rendered then Drupal can skip the
+ // unnecessary extra files.
+ //
+ // Learn more about attaching CSS and JavaScript libraries with the
+ // #attached property here:
+ // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21theme.api.php/group/theme_render/#sec_attached
+ $build['#attached'] = [
+ 'library' => [
+ 'render_example/render-example-css',
+ ],
+ ];
+
+ // Example of the marquee element type defined by
+ // \Drupal\render_example\Element\Marquee.
+ $build['marquee'] = [
+ '#description' => $this->t('Example custom element type'),
+ '#type' => 'markup',
+ 'marquee_element' => [
+ '#type' => 'marquee',
+ '#content' => $this->t('Hello world!'),
+ ],
+ ];
+
+ return $build;
+ }
+
+ /**
+ * A #pre_render callback, expand array to include additional example info.
+ *
+ * This method is called during the process of rendering the array generated
+ * by \Drupal\render_example\Controller\RenderExampleController::arrays().
+ *
+ * This also demonstrates how a #pre_render callback could be used to expand
+ * a relatively simple array into multiple individual renderable elements
+ * based on application logic.
+ *
+ * @param array $element
+ * Pre render methods (and functions) get a single argument that is the
+ * render API array representing the element where the #pre_render property
+ * was defined, and all of it's children.
+ *
+ * @return array
+ * Pre render methods (and functions) should return the modified render
+ * array.
+ */
+ public static function preRender(array $element) {
+ // For each first level child element lets add some additional helpful
+ // output. \Drupal\Core\Render\Element::children() is a utility method that
+ // allows you to quickly identify all children of a render array. That is
+ // those key/value pairs whose key does not start with a '#'.
+ foreach (Element::children($element) as $key) {
+ $child = $element[$key];
+ unset($element[$key]);
+ if (isset($child['#description'])) {
+ $element[$key] = [
+ // The value from the #description property will be used as a title
+ // for this element in the final output.
+ 'description' => [
+ '#markup' => $child['#description'],
+ ],
+ // Move the original element to 'rendered'. The rendering process is
+ // recursive so this will still be located, and rendered to HTML.
+ 'rendered' => $child,
+ // Export the element definition as a string of text so we can display
+ // the array that was used to create the rendered output just below the
+ // output.
+ 'unrendered' => [
+ '#markup' => htmlentities(Variable::export($child)),
+ ],
+ '#theme' => 'render_array',
+ ];
+ }
+ }
+
+ // Return our modified version of the original $element.
+ return $element;
+ }
+
+ /**
+ * Example #lazy_builder callback.
+ *
+ * Demonstrates the use of a #lazy_builder callback to build out a render
+ * array that can be substituted into the parent array wherever the cacheable
+ * placeholder exists.
+ *
+ * This method is called during the process of rendering the array generated
+ * by \Drupal\render_example\Controller\RenderExampleController::arrays().
+ *
+ * @param string $date_format
+ * Date format to use with \Drupal\Core\Datetime\DateFormatter::format().
+ *
+ * @return array
+ * A renderable array with content to replace the #lazy_builder placeholder.
+ */
+ public static function lazyBuilder($date_format) {
+ $build = [
+ 'lazy_builder_time' => [
+ '#markup' => '
' . \Drupal::translation()->translate('The current time is @time', [
+ '@time' => \Drupal::service('date.formatter')->format(REQUEST_TIME, 'custom', $date_format),
+ ]) . '
',
+ ],
+ ];
+
+ return $build;
+ }
+
+}
diff --git a/render_example/src/Element/Marquee.php b/render_example/src/Element/Marquee.php
new file mode 100644
index 0000000..1a5c301
--- /dev/null
+++ b/render_example/src/Element/Marquee.php
@@ -0,0 +1,92 @@
+ 'marquee',
+ * '#content' => 'Whoa cools, a marquee!',
+ * ];
+ * @endcode
+ *
+ * View an example of this custom element in use in
+ * \Drupal\render_example\Controller\RenderExampleController::arrays().
+ *
+ * @see plugin_api
+ * @see render_example_theme()
+ *
+ * @RenderElement("marquee")
+ */
+class Marquee extends RenderElement {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInfo() {
+
+ // Returns an array of default properties that will be merged with any
+ // properties defined in a render array when using this element type.
+ // You can use any standard render array property here, and you can also
+ // custom properties that are specific to your new element type.
+ return [
+ // See render_example_theme() where this new theme hook is declared.
+ '#theme' => 'render_example_marquee',
+ // Define a default #pre_render method. We will use this to handle
+ // additional processing for the custom attributes we add below.
+ '#pre_render' => [
+ [self::class, 'preRenderMarquee'],
+ ],
+ // This is a custom property for our element type. We set it to blank by
+ // default. The expectation is that a user will add the content that they
+ // would like to see inside the marquee tag. This custom property is
+ // accounted for in the associated template file.
+ '#content' => '',
+ '#attributes' => [
+ 'direction' => 'left',
+ 'loop' => -1,
+ 'scrollamount' => 'random',
+ ],
+ ];
+ }
+
+ /**
+ * Pre-render callback; Process custom attribute options.
+ *
+ * @param array $element
+ * The renderable array representing the element with '#type' => 'marquee'
+ * property set.
+ *
+ * @return array
+ * The passed in element with changes made to attributes depending on
+ * context.
+ */
+ public static function preRenderMarquee(array $element) {
+ // Normal attributes for a