diff --git a/modules/theming_example/css/theming_example.css b/modules/theming_example/css/theming_example.css new file mode 100644 index 0000000..a24c698 --- /dev/null +++ b/modules/theming_example/css/theming_example.css @@ -0,0 +1,11 @@ +/* + * style the list + * for OL you can have + * decimal | lower-roman | upper-roman | lower-alpha | upper-alpha + * for UL you can have + * disc | circle | square or an image eg url(x.png) + * you can also have 'none' + */ +ol.theming-example-list { + list-style-type: upper-alpha; +} diff --git a/modules/theming_example/src/Controller/ThemingPageController.php b/modules/theming_example/src/Controller/ThemingPageController.php new file mode 100644 index 0000000..3824f94 --- /dev/null +++ b/modules/theming_example/src/Controller/ThemingPageController.php @@ -0,0 +1,98 @@ + 'link', + '#url' => Url::fromRoute('theming_example.list'), + '#title' => t('Simple page with a list'), + ]; + $links[] = [ + '#type' => 'link', + '#url' => Url::fromRoute('theming_example.form_select'), + '#title' => t('Simple form 1'), + ]; + $links[] = [ + '#type' => 'link', + '#url' => Url::fromRoute('theming_example.form_text'), + '#title' => t('Simple form 2'), + ]; + $content = [ + '#theme' => 'item_list', + '#theme_wrappers' => ['theming_example_content_array'], + '#items' => $links, + '#title' => t('Some examples of pages and forms that are run through theme functions.'), + ]; + + return $content; + } + + /** + * The list page callback. + * + * An example page where the output is supplied as an array which is themed + * into a list and styled with css. + * + * In this case we'll use the core-provided theme_item_list as a #theme_wrapper. + * Any theme need only override theme_item_list to change the behavior. + */ + public function list() { + $items = [ + $this->t('First item'), + $this->t('Second item'), + $this->t('Third item'), + $this->t('Fourth item'), + ]; + + // First we'll create a render array that simply uses theme_item_list. + $title = $this->t("A list returned to be rendered using theme('item_list')"); + $build['render_version'] = [ + // We use #theme here instead of #theme_wrappers because theme_item_list() + // is the classic type of theme function that does not just assume a + // render array, but instead has its own properties (#type, #title, #items). + '#theme' => 'item_list', + // '#type' => 'ul', // The default type is 'ul' + // We can easily make sure that a css or js file is present using #attached. + '#attached' => ['library' => ['theming_example/list']], + '#title' => $title, + '#items' => $items, + '#attributes' => ['class' => ['render-version-list']], + ]; + + // Now we'll create a render array which uses our own list formatter, + // theme('theming_example_list'). + $title = $this->t("The same list rendered by theme('theming_example_list')"); + $build['our_theme_function'] = array( + '#theme' => 'theming_example_list', + '#attached' => ['library' => ['theming_example/list']], + '#title' => $title, + '#items' => $items, + ); + return $build; + } + +} diff --git a/modules/theming_example/src/Form/SelectForm.php b/modules/theming_example/src/Form/SelectForm.php new file mode 100644 index 0000000..d8198bf --- /dev/null +++ b/modules/theming_example/src/Form/SelectForm.php @@ -0,0 +1,56 @@ + $this->t('Newest first'), + 'newest_last' => $this->t('Newest last'), + 'edited_first' => $this->t('Edited first'), + 'edited_last' => $this->t('Edited last'), + 'by_name' => $this->t('By name'), + ]; + $form['choice'] = [ + '#type' => 'select', + '#options' => $options, + '#title' => $this->t('Choose which ordering you want'), + ]; + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Go'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->messenger()->addMessage($this->t('You chose %input', + ['%input' => $form_state->getValue('choice')])); + } + +} diff --git a/modules/theming_example/src/Form/TextForm.php b/modules/theming_example/src/Form/TextForm.php new file mode 100644 index 0000000..ae8f6dd --- /dev/null +++ b/modules/theming_example/src/Form/TextForm.php @@ -0,0 +1,49 @@ + 'textfield', + '#title' => t('Please input something!'), + '#required' => TRUE, + ]; + $form['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Go'), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->messenger()->addMessage($this->t('You chose %input', + ['%input' => $form_state->getValue('text')])); + } + +} diff --git a/modules/theming_example/templates/theming-example-content-array.html.twig b/modules/theming_example/templates/theming-example-content-array.html.twig new file mode 100644 index 0000000..372c8b4 --- /dev/null +++ b/modules/theming_example/templates/theming-example-content-array.html.twig @@ -0,0 +1,18 @@ +{# +/** + * @file + * Theme a simple content array. + * + * This template uses the newer recommended format where a single + * render array is provided to the theme function. +*/ +#} +{% for item in element['#items'] %} + {% if not loop.index %} + {# The first paragraph is bolded. #} +

{{ item }}

+ {% else %} + {# Following paragraphs are just output as routine paragraphs. #} +

{{ item }}

+ {% endif %} +{% endfor %} diff --git a/modules/theming_example/templates/theming-example-list.html.twig b/modules/theming_example/templates/theming-example-list.html.twig new file mode 100644 index 0000000..cddd070 --- /dev/null +++ b/modules/theming_example/templates/theming-example-list.html.twig @@ -0,0 +1 @@ +{{ content }} diff --git a/modules/theming_example/templates/theming-example-text-form.html.twig b/modules/theming_example/templates/theming-example-text-form.html.twig new file mode 100644 index 0000000..2162aec --- /dev/null +++ b/modules/theming_example/templates/theming-example-text-form.html.twig @@ -0,0 +1,29 @@ +{# +/** + * @file + * Template file for the theming example text form. + * + * Available custom variables: + * - $text_form: A string containing the pre-rendered form. + * - $text_form_content: An array of form elements keyed by the element name. + * + * The default example below renders the entire form and its form elements in + * a default order provided by Drupal. + * + * Alternatively, you may print each form element in the order you desire, + * adding any extra html markup you wish to decorate the form like this: + * + * + * + * The following snippet will print the contents of the $text_form_content + * array, hidden in the source of the page, for you to discover the individual + * element names. + * + * '; ?> + */ +#} + +
+{{ text_form }} +
+ diff --git a/modules/theming_example/tests/src/Functional/ThemingExampleTest.php b/modules/theming_example/tests/src/Functional/ThemingExampleTest.php new file mode 100644 index 0000000..98a290d --- /dev/null +++ b/modules/theming_example/tests/src/Functional/ThemingExampleTest.php @@ -0,0 +1,72 @@ +) and has + // content. + $this->drupalGet('/examples/theming_example'); + $this->assertRaw('Some examples of pages and forms that are run through theme functions.'); + $this->assertRaw('examples/theming_example/form_select">Simple form 1'); + $this->assertRaw('examples/theming_example/form_text">Simple form 2'); + + // Visit the list demonstration page and check that css gets loaded + // and do some spot checks on how the two lists were themed. + $this->drupalGet('/examples/theming_example/theming_example_list_page'); + // CSS should be always injected, because preprocess is set to false in *.libraries.yml + $this->assertPattern('/xpath('//ul[contains(@class,"render-version-list")]'); + $this->assertTrue($first_ul[0]->li[0] == 'First item'); + $second_ul = $this->xpath('//ol[contains(@class,"theming-example-list")]'); + $this->assertTrue($second_ul[0]->li[1] == 'Second item'); + + // Visit the select form page to do spot checks. + $this->drupalGet('/examples/theming_example/form_select'); + // We did explicit theming to accomplish the below... + $this->assertRaw('Choose which ordering you want'); + $this->assertRaw('
'); + $this->assertNoPattern('/drupalGet('/examples/theming_example/form_text'); + $this->assertText('Please input something!'); + // If it were themed normally there would be a div wrapper in our pattern. + $this->assertPattern('%
\s* 'Theming Example', + 'description' => 'Some theming examples.', + 'page callback' => 'theming_example_page', + 'access callback' => TRUE, + 'access arguments' => array('access content'), + ); + $items['examples/theming_example/theming_example_list_page'] = array( + 'title' => 'Theming a list', + 'page callback' => 'theming_example_list_page', + 'access arguments' => array('access content'), + 'weight' => 1, + ); + $items['examples/theming_example/theming_example_select_form'] = array( + 'title' => 'Theming a form (select form)', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('theming_example_select_form'), + 'access arguments' => array('access content'), + 'weight' => 2, + ); + $items['examples/theming_example/theming_example_text_form'] = array( + 'title' => 'Theming a form (text form)', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('theming_example_text_form'), + 'access arguments' => array('access content'), + 'weight' => 3, + ); + + return $items; + +} + +/** + * Implements hook_theme(). + * + * Defines the theming capabilities provided by this module. + */ +function theming_example_theme($existing, $type, $theme, $path) { + return array( + 'theming_example_content_array' => array( + // We use 'render element' when the item to be passed is a self-describing + // render array (it will have #theme_wrappers) + 'render element' => 'element', + ), + 'theming_example_list' => array( + // We use 'variables' when the item to be passed is an array whose + // structure must be described here. + 'variables' => array( + 'title' => NULL, + 'items' => NULL, + ), + ), + 'theming_example_select_form' => array( + 'render element' => 'form', + ), + 'theming_example_text_form' => array( + 'render element' => 'form', + // In this one the rendering will be done by a template file + // (theming-example-text-form.tpl.php) instead of being rendered by a + // function. Note the use of dashes to separate words in place of + // underscores. The template file's extension is also left out so that + // it may be determined automatically depending on the template engine + // the site is using. + 'template' => 'theming-example-text-form', + ), + ); +} +function theming_example_page() { + $content[]['#markup'] = t('Some examples of pages and forms that are run through theme functions.'); + $content[]['#markup'] = l(t('Simple page with a list'), 'examples/theming_example/theming_example_list_page'); + $content[]['#markup'] = l(t('Simple form 1'), 'examples/theming_example/theming_example_select_form'); + $content[]['#markup'] = l(t('Simple form 2'), 'examples/theming_example/theming_example_text_form'); + $content['#theme_wrappers'] = array('theming_example_content_array'); + return $content; +} + +/** + * Theming a simple form. + * + * Since our form is named theming_example_select_form(), the default + * #theme function applied to is will be 'theming_example_select_form' + * if it exists. The form could also have specified a different + * #theme. + * + * Here we collect the title, theme it manually and + * empty the form title. We also wrap the form in a div. + */ +function theme_theming_example_select_form($variables) { + $form = $variables['form']; + $title = $form['choice']['#title']; + $form['choice']['#title'] = ''; + $output = '' . $title . ''; + $form['choice']['#prefix'] = '
'; + $form['submit']['#suffix'] = '
'; + $output .= drupal_render_children($form); + return $output; +} + +/** + * Implements template_preprocess(). + * + * We prepare variables for use inside the theming-example-text-form.tpl.php + * template file. + * + * In this example, we create a couple new variables, 'text_form' and + * 'text_form_content', that clean up the form output. Drupal will turn the + * array keys in the $variables array into variables for use in the template. + * + * So $variables['text_form'] becomes available as $text_form in the template. + * + * @see theming-example-text-form.tpl.php + */ +function template_preprocess_theming_example_text_form(&$variables) { + $variables['text_form_content'] = array(); + $text_form_hidden = array(); + + // Each form element is rendered and saved as a key in $text_form_content, to + // give the themer the power to print each element independently in the + // template file. Hidden form elements have no value in the theme, so they + // are grouped into a single element. + foreach (element_children($variables['form']) as $key) { + $type = $variables['form'][$key]['#type']; + if ($type == 'hidden' || $type == 'token') { + $text_form_hidden[] = drupal_render($variables['form'][$key]); + } + else { + $variables['text_form_content'][$key] = drupal_render($variables['form'][$key]); + } + } + $variables['text_form_content']['hidden'] = implode($text_form_hidden); + + // The entire form is then saved in the $text_form variable, to make it easy + // for the themer to print the whole form. + $variables['text_form'] = implode($variables['text_form_content']); +} +/** + * @} End of "defgroup theming_example". + */ diff --git a/modules/theming_example/theming_example.routing.yml b/modules/theming_example/theming_example.routing.yml new file mode 100644 index 0000000..9c407be --- /dev/null +++ b/modules/theming_example/theming_example.routing.yml @@ -0,0 +1,32 @@ +# Main page of the theming example. +theming_example.entry: + path: '/examples/theming_example' + defaults: + _controller: '\Drupal\theming_example\Controller\ThemingPageController::entryPage' + _title: 'Theming example' + requirements: + _permission: 'access content' + +theming_example.list: + path: '/examples/theming_example/list' + defaults: + _controller: '\Drupal\theming_example\Controller\ThemingPageController::list' + _title: 'Theming a list' + requirements: + _permission: 'access content' + +theming_example.form_select: + path: '/examples/theming_example/form_select' + defaults: + _form: '\Drupal\theming_example\Form\SelectForm' + _title: 'Theming a form (select form)' + requirements: + _permission: 'access content' + +theming_example.form_text: + path: '/examples/theming_example/form_text' + defaults: + _form: '\Drupal\theming_example\Form\TextForm' + _title: 'Theming a form (text form)' + requirements: + _permission: 'access content'