diff --git a/config/schema/panels.schema.yml b/config/schema/panels.schema.yml index 4085349..5021232 100644 --- a/config/schema/panels.schema.yml +++ b/config/schema/panels.schema.yml @@ -20,3 +20,14 @@ display_variant.plugin.panels_variant: storage_id: type: string label: Storage Id + css_classes: + type: sequence + label: The list of the display variant CSS classes + sequence: + type: string + html_id: + type: string + label: The HTML Id. of the display variant + css_styles: + type: string + label: The inline CSS styles of the display variant diff --git a/panels.module b/panels.module index fae0eac..c5a3d6c 100644 --- a/panels.module +++ b/panels.module @@ -111,3 +111,26 @@ function panels_form_page_manager_add_variant_form_submit(array &$form, FormStat $plugin->setStorage('page_manager', $page_variant->id()); } } + +/** + * Implements hook_config_schema_info_alter(). + */ +function panels_config_schema_info_alter(&$definitions) { + $definitions['ctools.block_plugin.*']['mapping'] += [ + 'css_classes' => [ + 'type' => 'sequence', + 'label' => 'The list of the display variant CSS classes', + 'sequence' => [ + 'type' => 'string', + ], + ], + 'html_id' => [ + 'type' => 'string', + 'label' => 'The HTML Id. of the display variant', + ], + 'css_styles' => [ + 'type' => 'string', + 'label' => 'The inline CSS styles of the display variant', + ], + ]; +} diff --git a/panels_ipe/src/Form/PanelsIPEBlockPluginForm.php b/panels_ipe/src/Form/PanelsIPEBlockPluginForm.php index a13ba9a..4a2a5e6 100644 --- a/panels_ipe/src/Form/PanelsIPEBlockPluginForm.php +++ b/panels_ipe/src/Form/PanelsIPEBlockPluginForm.php @@ -3,6 +3,7 @@ namespace Drupal\panels_ipe\Form; use Drupal\Component\Plugin\PluginManagerInterface; +use Drupal\Component\Utility\Html; use Drupal\Core\Block\BlockPluginInterface; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormState; @@ -11,6 +12,7 @@ use Drupal\Core\Plugin\Context\ContextHandlerInterface; use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait; use Drupal\Core\Plugin\ContextAwarePluginInterface; use Drupal\Core\Render\RendererInterface; +use Drupal\panels\Form\PanelsStyleTrait; use Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant; use Drupal\panels_ipe\PanelsIPEBlockRendererTrait; use Drupal\Core\TempStore\SharedTempStoreFactory; @@ -27,6 +29,7 @@ class PanelsIPEBlockPluginForm extends FormBase { use ContextAwarePluginAssignmentTrait; use PanelsIPEBlockRendererTrait; + use PanelsStyleTrait; /** * @var \Drupal\Component\Plugin\PluginManagerInterface @@ -183,6 +186,7 @@ class PanelsIPEBlockPluginForm extends FormBase { '#required' => TRUE, '#default_value' => $region, ]; + $form['flipper']['front']['settings'] += $this->getCssStyleForm($block_config, TRUE); // Add an add button, which is only used by our App. $form['submit'] = [ @@ -276,6 +280,21 @@ class PanelsIPEBlockPluginForm extends FormBase { // Set the block region appropriately. $block_config = $block_instance->getConfiguration(); $block_config['region'] = $form_state->getValue(['settings', 'region']); + $block_config['css_classes'] = preg_split('/\s+/', trim($form_state->getValue([ + 'settings', + 'style_settings', + 'css_classes', + ]))); + $block_config['html_id'] = $form_state->getValue([ + 'settings', + 'style_settings', + 'html_id', + ]); + $block_config['css_styles'] = $form_state->getValue([ + 'settings', + 'style_settings', + 'css_styles', + ]); // Determine if we need to update or add this block. if ($uuid = $form_state->getValue('uuid')) { @@ -298,6 +317,19 @@ class PanelsIPEBlockPluginForm extends FormBase { // Add our data attribute for the Backbone app. $build['#attributes']['data-block-id'] = $uuid; + // Add CSS classes. + foreach ($block_config['css_classes'] as $class) { + $build['#attributes']['class'][] = Html::cleanCssIdentifier($class); + } + // Add HTML Id. + if (!empty($block_config['html_id'])) { + $build['#attributes']['id'] = Html::getId($block_config['html_id']); + } + // Add CSS styles. + if (!empty($block_config['css_styles'])) { + $build['#attributes']['style'] = $block_config['css_styles']; + } + $plugin_definition = $block_instance->getPluginDefinition(); $block_model = [ @@ -344,6 +376,30 @@ class PanelsIPEBlockPluginForm extends FormBase { // Gather a render array for the block. $build = $this->buildBlockInstance($block_instance, $this->panelsDisplay); + // Add CSS classes. + $css_classes = preg_split('/\s+/', trim($form_state->getValue([ + 'settings', + 'style_settings', + 'css_classes', + ]))); + foreach ($css_classes as $class) { + $build['#attributes']['class'][] = Html::cleanCssIdentifier($class); + } + // Add HTML Id. + $html_id = $form_state->getValue(['settings', 'style_settings', 'html_id']); + if (!empty($html_id)) { + $build['#attributes']['id'] = Html::getId($html_id); + } + // Add CSS styles. + $css_styles = $form_state->getValue([ + 'settings', + 'style_settings', + 'css_styles', + ]); + if (!empty($css_styles)) { + $build['#attributes']['style'] = $css_styles; + } + // Replace any nested form tags from the render array. $build['content']['#post_render'][] = function ($html, array $elements) { $search = ['']; diff --git a/panels_ipe/tests/src/FunctionalJavascript/PageManagerIntegrationTest.php b/panels_ipe/tests/src/FunctionalJavascript/PageManagerIntegrationTest.php index 0922698..bfc495d 100644 --- a/panels_ipe/tests/src/FunctionalJavascript/PageManagerIntegrationTest.php +++ b/panels_ipe/tests/src/FunctionalJavascript/PageManagerIntegrationTest.php @@ -92,4 +92,69 @@ class PageManagerIntegrationTest extends PanelsIPETestBase { $this->assertSession()->elementExists('css', '.layout--onecol'); } + /** + * Test IPE with Panels block preview and custom CSS properties. + */ + public function testBlockPreviewAndCustomCssProperties() { + $page = $this->getSession()->getPage(); + $this->visitIPERoute(); + // Visit IPE page and add block. (@see PanelsIPETestTrait addBlock() method) + $this->clickAndWait('[data-tab-id="manage_content"]'); + $this->waitUntilNotPresent('.ipe-icon-loading'); + $this->clickAndWait('[data-category="System"]'); + $this->getSession()->executeScript("jQuery('" . '[data-plugin-id="system_powered_by_block"]' . "')[0].click()"); + $this->waitUntilNotPresent('.ipe-icon-loading'); + $this->waitUntilVisible('.ipe-form form'); + $this->clickAndWait('[data-drupal-selector="edit-settings-style-settings"]'); + // Generate random HTML Id., CSS classes and styles. + $css = $this->generateCssProperties(); + // Fill settings fields. + $page->fillField('settings[style_settings][html_id]', $css['html_id']); + $page->fillField('settings[style_settings][css_classes]', $css['css_classes']); + $page->fillField('settings[style_settings][css_styles]', $css['css_style']); + // Click preview button and check result. + $this->clickAndWait('[data-drupal-selector="edit-preview"]'); + $this->assertSession()->elementExists('css', '#' . $css['html_id']); + $this->assertSession()->elementExists('css', '.' . str_replace(' ', '.', $css['css_classes'])); + $this->assertSession()->elementExists('css', '[style*="' . $css['css_style'] . '"]'); + // Click preview button and re-fill settings form with new values. + $this->clickAndWait('[data-drupal-selector="edit-preview"]'); + $this->clickAndWait('[data-drupal-selector="edit-settings-style-settings"]'); + $css = $this->generateCssProperties(); + $page->fillField('settings[style_settings][html_id]', $css['html_id']); + $page->fillField('settings[style_settings][css_classes]', $css['css_classes']); + $page->fillField('settings[style_settings][css_styles]', $css['css_style']); + $this->saveBlockConfigurationForm(); + $this->waitUntilNotPresent('.ipe-icon-loading'); + $this->clickAndWait('[data-tab-id="save"]'); + $this->assertSession()->elementExists('css', '#' . $css['html_id']); + $this->assertSession()->elementExists('css', '.' . str_replace(' ', '.', $css['css_classes'])); + $this->assertSession()->elementExists('css', '[style*="' . $css['css_style'] . '"]'); + } + + /** + * Test IPE with Panels block edit and custom CSS properties. + */ + public function testBlockEditAndCustomCssProperties() { + $page = $this->getSession()->getPage(); + $this->visitIPERoute(); + $this->addBlock('System', 'system_breadcrumb_block'); + // Edit block settings and fill style-settings with random values. + $this->clickAndWait('[data-block-edit-id="system_breadcrumb_block"] [data-action-id="configure"]'); + // Wait for the Block form to finish loading/opening. + $this->waitUntilNotPresent('.ipe-icon-loading'); + $this->waitUntilVisible('.ipe-form form'); + $this->clickAndWait('[data-drupal-selector="edit-settings-style-settings"]'); + // Generate random HTML Id., CSS classes and styles. + $css = $this->generateCssProperties(); + $page->fillField('settings[style_settings][html_id]', $css['html_id']); + $page->fillField('settings[style_settings][css_classes]', $css['css_classes']); + $page->fillField('settings[style_settings][css_styles]', $css['css_style']); + $this->saveBlockConfigurationForm(); + $this->clickAndWait('[data-tab-id="save"]'); + $this->assertSession()->elementExists('css', '#' . $css['html_id']); + $this->assertSession()->elementExists('css', '.' . str_replace(' ', '.', $css['css_classes'])); + $this->assertSession()->elementExists('css', '[style*="' . $css['css_style'] . '"]'); + } + } diff --git a/panels_ipe/tests/src/FunctionalJavascript/PanelsIPETestTrait.php b/panels_ipe/tests/src/FunctionalJavascript/PanelsIPETestTrait.php index 0cfbe12..a810ba8 100644 --- a/panels_ipe/tests/src/FunctionalJavascript/PanelsIPETestTrait.php +++ b/panels_ipe/tests/src/FunctionalJavascript/PanelsIPETestTrait.php @@ -297,4 +297,25 @@ trait PanelsIPETestTrait { $this->assertJsCondition($condition, 10000); } + /** + * Generate random CSS properties (HTML Id., classes, style). + * + * @param int $class_item_count + * Class item counter. + * + * @return array + * Keyed array with generated CSS properties. + */ + public function generateCssProperties($class_item_count = 5) { + $result = []; + $result['html_id'] = strtolower($this->randomMachineName()); + $css_classes_array = []; + for ($i = 0; $i < $class_item_count; $i++) { + $css_classes_array[] = strtolower($this->randomMachineName()); + } + $result['css_classes'] = implode(' ', $css_classes_array); + $result['css_style'] = strtolower($this->randomMachineName() . ': ' . $this->randomMachineName()); + return $result; + } + } diff --git a/src/Form/PanelsBlockConfigureFormBase.php b/src/Form/PanelsBlockConfigureFormBase.php index b740d8b..3a172d6 100644 --- a/src/Form/PanelsBlockConfigureFormBase.php +++ b/src/Form/PanelsBlockConfigureFormBase.php @@ -18,6 +18,7 @@ abstract class PanelsBlockConfigureFormBase extends FormBase { use ContextAwarePluginAssignmentTrait; use CachedValuesGetterTrait; + use PanelsStyleTrait; /** * Tempstore factory. @@ -125,6 +126,8 @@ abstract class PanelsBlockConfigureFormBase extends FormBase { $form_state->setTemporaryValue('gathered_contexts', $contexts); $this->block = $this->prepareBlock($block_id); + $settings = $this->block->getConfiguration(); + $form += $this->getCssStyleForm($settings); $form_state->set('machine_name', $machine_name); $form_state->set('block_id', $this->block->getConfiguration()['uuid']); @@ -186,6 +189,9 @@ abstract class PanelsBlockConfigureFormBase extends FormBase { $configuration = $this->block->getConfiguration(); $configuration['region'] = $form_state->getValue('region'); + $configuration['css_classes'] = preg_split('/\s+/', trim($form_state->getValue('css_classes'))); + $configuration['html_id'] = $form_state->getValue('html_id'); + $configuration['css_styles'] = $form_state->getValue('css_styles'); $this->getVariantPlugin()->updateBlock($this->block->getConfiguration()['uuid'], $configuration); $cached_values = $this->getCachedValues($this->tempstore, $this->tempstore_id, $form_state->get('machine_name')); diff --git a/src/Form/PanelsContentForm.php b/src/Form/PanelsContentForm.php index 4585829..8014fcd 100644 --- a/src/Form/PanelsContentForm.php +++ b/src/Form/PanelsContentForm.php @@ -15,6 +15,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; class PanelsContentForm extends FormBase { use AjaxFormTrait; + use PanelsStyleTrait; /** * Tempstore factory. @@ -103,6 +104,7 @@ class PanelsContentForm extends FormBase { '#description' => $this->t('Configure the page title that will be used for this display.'), '#default_value' => $variant_plugin->getConfiguration()['page_title'] ?: '', ]; + $form += $this->getCssStyleForm($variant_plugin->getConfiguration()); $pattern_plugin = $variant_plugin->getPattern(); $machine_name = $pattern_plugin->getMachineName($cached_values); @@ -264,12 +266,23 @@ class PanelsContentForm extends FormBase { $variant_plugin->updateBlock($block_id, $block_values); } } + $configuration = $variant_plugin->getConfiguration(); // Page Variant title handling. if ($form_state->hasValue('page_title')) { - $configuration = $variant_plugin->getConfiguration(); $configuration['page_title'] = $form_state->getValue('page_title'); $variant_plugin->setConfiguration($configuration); } + // Page Variant CSS classes, Id, styles handling. + if ($form_state->hasValue('css_classes')) { + $configuration['css_classes'] = preg_split('/\s+/', trim($form_state->getValue('css_classes'))); + } + if ($form_state->hasValue('html_id')) { + $configuration['html_id'] = $form_state->getValue('html_id'); + } + if ($form_state->hasValue('css_styles')) { + $configuration['css_styles'] = $form_state->getValue('css_styles'); + } + $variant_plugin->setConfiguration($configuration); } /** diff --git a/src/Form/PanelsStyleTrait.php b/src/Form/PanelsStyleTrait.php new file mode 100644 index 0000000..ae8c51b --- /dev/null +++ b/src/Form/PanelsStyleTrait.php @@ -0,0 +1,50 @@ + 'details', + '#title' => $this->t('Style settings'), + '#open' => FALSE, + '#tree' => $tree, + ]; + $form['style_settings']['css_classes'] = [ + '#title' => $this->t('CSS classes'), + '#type' => 'textfield', + '#default_value' => !empty($default_value['css_classes']) ? implode(' ', $default_value['css_classes']) : NULL, + '#description' => $this->t('Customize the element style by adding CSS classes. Separate multiple classes by spaces.'), + ]; + $form['style_settings']['html_id'] = [ + '#title' => $this->t('HTML Id.'), + '#type' => 'textfield', + '#default_value' => !empty($default_value['html_id']) ? $default_value['html_id'] : NULL, + '#description' => $this->t('Customize the element style by adding CSS #id.'), + ]; + $form['style_settings']['css_styles'] = [ + '#title' => $this->t('CSS styles'), + '#type' => 'textarea', + '#default_value' => !empty($default_value['css_styles']) ? $default_value['css_styles'] : NULL, + '#description' => $this->t('Customize the element style by adding inline CSS.'), + ]; + return $form; + } + +} \ No newline at end of file diff --git a/src/Plugin/DisplayBuilder/StandardDisplayBuilder.php b/src/Plugin/DisplayBuilder/StandardDisplayBuilder.php index b1f5a32..bde30aa 100644 --- a/src/Plugin/DisplayBuilder/StandardDisplayBuilder.php +++ b/src/Plugin/DisplayBuilder/StandardDisplayBuilder.php @@ -120,12 +120,13 @@ class StandardDisplayBuilder extends DisplayBuilderBase implements PluginWizardI $block->setTitle($title); } if ($block->access($this->account)) { + $configuration = $block->getConfiguration(); $block_render_array = [ '#theme' => 'block', '#attributes' => [], '#contextual_links' => [], '#weight' => $weight++, - '#configuration' => $block->getConfiguration(), + '#configuration' => $configuration, '#plugin_id' => $block->getPluginId(), '#base_plugin_id' => $block->getBaseId(), '#derivative_plugin_id' => $block->getDerivativeId(), @@ -159,6 +160,26 @@ class StandardDisplayBuilder extends DisplayBuilderBase implements PluginWizardI $block_render_array['content'] = $content; + // Add CSS classes. + $css_classes = !empty($configuration['css_classes']) ? $configuration['css_classes'] : []; + if (is_array($css_classes)) { + foreach ($css_classes as $class) { + $block_render_array['#attributes']['class'][] = Html::cleanCssIdentifier($class); + } + } elseif (is_string($css_classes)) { + $block_render_array['#attributes']['class'][] = Html::cleanCssIdentifier($css_classes); + } + // Add HTML Id. + $html_id = !empty($configuration['html_id']) ? $configuration['html_id'] : ''; + if (!empty($html_id)) { + $block_render_array['#attributes']['id'] = Html::getId($html_id); + } + // Add CSS styles. + $css_styles = !empty($configuration['css_styles']) ? $configuration['css_styles'] : ''; + if (!empty($css_styles)) { + $block_render_array['#attributes']['style'] = $css_styles; + } + $this->moduleHandler->alter(['block_view', 'block_view_' . $block->getBaseId()], $block_render_array, $block); $build[$region][$block_id] = $block_render_array; } diff --git a/src/Plugin/DisplayVariant/PanelsDisplayVariant.php b/src/Plugin/DisplayVariant/PanelsDisplayVariant.php index 210f7e9..5a01b53 100644 --- a/src/Plugin/DisplayVariant/PanelsDisplayVariant.php +++ b/src/Plugin/DisplayVariant/PanelsDisplayVariant.php @@ -2,6 +2,7 @@ namespace Drupal\panels\Plugin\DisplayVariant; +use Drupal\Component\Utility\Html; use Drupal\Component\Render\HtmlEscapedText; use Drupal\Component\Uuid\UuidInterface; use Drupal\Core\Block\BlockManager; @@ -338,6 +339,24 @@ class PanelsDisplayVariant extends BlockDisplayVariant implements PluginWizardIn $build = $this->getBuilder()->build($this); $build['#title'] = $this->getRenderedPageTitle(); + // Add CSS classes. + $css_classes = !empty($this->configuration['css_classes']) ? $this->configuration['css_classes'] : []; + foreach ($css_classes as $class) { + $build['#attributes']['class'][] = Html::cleanCssIdentifier($class); + } + + // Add HTML Id. + $html_id = !empty($this->configuration['html_id']) ? $this->configuration['html_id'] : ''; + if (!empty($html_id)) { + $build['#attributes']['id'] = Html::getId($html_id); + } + + // Add CSS styles. + $css_styles = !empty($this->configuration['css_styles']) ? $this->configuration['css_styles'] : ''; + if (!empty($css_styles)) { + $build['#attributes']['style'] = $css_styles; + } + // Allow other module to alter the built panel. $this->moduleHandler->alter('panels_build', $build, $this); @@ -381,6 +400,9 @@ class PanelsDisplayVariant extends BlockDisplayVariant implements PluginWizardIn $this->configuration['builder'] = $form_state->getValue('builder'); } $this->configuration['page_title'] = $form_state->getValue('page_title'); + $this->configuration['css_classes'] = preg_split('/\s+/', trim($form_state->getValue('css_classes'))); + $this->configuration['html_id'] = $form_state->getValue('html_id'); + $this->configuration['css_styles'] = $form_state->getValue('css_styles'); } /** @@ -406,6 +428,9 @@ class PanelsDisplayVariant extends BlockDisplayVariant implements PluginWizardIn 'page_title' => '', 'storage_type' => '', 'storage_id' => '', + 'css_classes' => [], + 'html_id' => '', + 'css_styles' => '', ]; } diff --git a/tests/modules/panels_test/templates/layout-example-test.html.twig b/tests/modules/panels_test/templates/layout-example-test.html.twig index 3cabd71..c66fb19 100644 --- a/tests/modules/panels_test/templates/layout-example-test.html.twig +++ b/tests/modules/panels_test/templates/layout-example-test.html.twig @@ -1,18 +1,20 @@ -{# -/** - * @file - * Template for layout_example_test layout. - */ -#} -
-
- Blah: - {{ settings.setting_1 }} +{% + set classes = [ + 'layout-example-test', + 'clearfix', +] +%} +{% if content %} + +
+ Blah: + {{ settings.setting_1 }} +
+
+ {{ content.top }} +
+
+ {{ content.bottom }} +
-
- {{ content.top }} -
-
- {{ content.bottom }} -
-
+{% endif %} \ No newline at end of file