Drupal 8 & 9: The example how create a configurable block programmatically
26
Jul.2014
Image

In this example, we will look at just a few examples.
- how to create the programmatically configurable block with fields;
- how to create a controller;
- how to integrate your module with 3d party libraries
The code below will be considered as an example of the "Sticky Sharrre Bar" module that I have created a couple of years ago.
Let's go:
File
sticky_sharrre_bar.info.yml
name: Sticky Sharrre Bar description: Provides a sticky <a href="http://sharrre.com/">Sharrre</a> block on your site. version: VERSION core: 8.x package: Sharing dependencies: - block type: module configure: block.admin_display
File
sticky_sharrre_bar.permissions.yml
'access sticky_sharrre_bar': title: 'View Sticky Sharrre Bar Block'
File
sticky_sharrre_bar.routing.yml
sticky_sharrre_bar.sticky_sharrre_bar_controller_sharrre: path: 'sharrre' defaults: _controller: '\Drupal\sticky_sharrre_bar\Controller\StickySharrreBarController::sharrre' _title: 'Emulation functional of php file from http://sharrre.com.' requirements: _permission: 'access sticky_sharrre_bar'
File
sticky_sharrre_bar.install
<?php /** * @file * Install, update and uninstall functions for the sticky_sharrre_bar module. */ use Drupal\Component\Utility\Unicode; define('WAYPOINTS_MIN_PLUGIN_VERSION', '4.0.0'); define('SHARRRE_PLUGIN_VERSION', '1.3.5'); /** * Implements hook_requirements(). */ function sticky_sharrre_bar_requirements($phase) { $requirements = array(); if (\Drupal::moduleHandler()->moduleExists('libraries')) { if ($phase == 'runtime') { $library = libraries_detect('jquery-waypoints'); $error_type = isset($library['error']) ? Unicode::ucfirst($library['error']) : ''; $error_message = isset($library['error message']) ? $library['error message'] : ''; if (empty($library['installed'])) { $requirements['jquery_waypoints_plugin'] = array( 'title' => t('jQuery Waypoints plugin'), 'value' => t('@e: At least @a', array( '@e' => $error_type, '@a' => WAYPOINTS_MIN_PLUGIN_VERSION, )), 'severity' => REQUIREMENT_ERROR, 'description' => t('@error You need to download the <a href=":jquery_waypoints">jQuery Waypoints plugin</a>, extract the archive and place the jquery-waypoints directory in the %path directory on your server.', array( '@error' => $error_message, ':jquery_waypoints' => $library['download url'], '%path' => 'libraries', )), ); } elseif (version_compare(trim($library['version']), WAYPOINTS_MIN_PLUGIN_VERSION, '>=')) { $requirements['jquery_waypoints_plugin'] = array( 'title' => t('jQuery Waypoints plugin'), 'severity' => REQUIREMENT_OK, 'value' => $library['version'], ); } else { $requirements['jquery_waypoints_plugin'] = array( 'title' => t('jQuery Waypoints plugin'), 'value' => t('At least @a', array('@a' => WAYPOINTS_MIN_PLUGIN_VERSION)), 'severity' => REQUIREMENT_ERROR, 'description' => t('You need to download a later version of the <a href=":jquery_waypoints">jQuery Waypoints plugin</a> and replace the old version located in the %path directory on your server.', array( ':jquery_waypoints' => $library['download url'], '%path' => $library['library path'], )), ); } $library = libraries_detect('sharrre'); $error_type = isset($library['error']) ? Unicode::ucfirst($library['error']) : ''; $error_message = isset($library['error message']) ? $library['error message'] : ''; if (empty($library['installed'])) { $requirements['sharrre_plugin'] = array( 'title' => t('jQuery Sharrre plugin'), 'value' => t('@e: At least @a', array( '@e' => $error_type, '@a' => SHARRRE_PLUGIN_VERSION, )), 'severity' => REQUIREMENT_ERROR, 'description' => t('@error You need to download the <a href=":sharrre">jQuery Sharrre plugin</a>, extract the archive and place the sharrre directory in the %path directory on your server.', array( '@error' => $error_message, ':sharrre' => $library['download url'], '%path' => 'libraries', )), ); } elseif (version_compare(trim($library['version']), SHARRRE_PLUGIN_VERSION, '==')) { $requirements['sharrre_plugin'] = array( 'title' => t('jQuery Sharrre plugin'), 'severity' => REQUIREMENT_OK, 'value' => $library['version'], ); } else { $requirements['sharrre_plugin'] = array( 'title' => t('jQuery Sharrre plugin'), 'value' => t('Requires @a', array('@a' => SHARRRE_PLUGIN_VERSION)), 'severity' => REQUIREMENT_ERROR, 'description' => t('You need to download the 1.3.5 version of the <a href=":sharrre">jQuery Sharrre plugin</a> and replace the old version located in the %path directory on your server.', array( ':sharrre' => $library['download url'], '%path' => $library['library path'], )), ); } } } return $requirements; }
In this file, we define dependencies and libraries that are necessary for our module.
File
src\Controller\StickySharrreBarController.php
<?php namespace Drupal\sticky_sharrre_bar\Controller; use Drupal\Core\Controller\ControllerBase; use Drupal\Component\Utility\Html; use Drupal\Component\Serialization\Json; use Symfony\Component\HttpFoundation\JsonResponse; /** * Controller routines for Sticky Sharrre Bar route. * * @package Drupal\sticky_sharrre_bar\Controller */ class StickySharrreBarController extends ControllerBase { /** * Emulation functional of php file from http://sharrre.com. * * @return array * A render array representing the administrative page content. */ public function sharrre() { $url = $_GET['url']; $type = $_GET['type']; $output = array('url' => $url, 'count' => 0); // We shouldn't cache the router, to avoid the same result for all pages, // like {"url": null, "count": 0} \Drupal::service('page_cache_kill_switch')->trigger(); if (filter_var($url, FILTER_VALIDATE_URL)) { if ($type == 'googlePlus') { $contents = $this->stickySharrreBarParse('https://plusone.google.com/u/0/_/+1/fastbutton?url=' . $url . '&count=true'); preg_match('/window\.__SSR = {c: ([\d]+)/', $contents, $matches); if (isset($matches[0])) { $output['count'] = (int) str_replace('window.__SSR = {c: ', '', $matches[0]); } } else { if ($type == 'stumbleupon') { $content = $this->stickySharrreBarParse('http://www.stumbleupon.com/services/1.01/badge.getinfo?url=' . $url); $result = Json::decode($content); if (isset($result->result->views)) { $output['count'] = Html::escape($result->result->views); } } } } return new JsonResponse($output); } /** * Get necessary content use cURL. * * @param string $encoded_url * Url of page. * * @return \Psr\Http\Message\StreamInterface * Ready data */ private function stickySharrreBarParse($encoded_url) { $client = \Drupal::httpClient(); $request = $client->get($encoded_url); return $request->getBody(); } }
File
src\Plugin\Block\StickySharrreBarBlock.php
<?php namespace Drupal\sticky_sharrre_bar\Plugin\Block; use Drupal\Core\Block\BlockBase; use Drupal\block\Entity\Block; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Access\AccessResult; use Drupal\Component\Utility\Html; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\Core\Link; /** * Provides a 'StickySharrreBarBlock' block. * * @Block( * id = "sticky_sharrre_bar_block", * admin_label = @Translation("Sticky sharrre bar"), * ) */ class StickySharrreBarBlock extends BlockBase { /** * {@inheritdoc} */ protected function blockAccess(AccountInterface $account) { return AccessResult::allowedIfHasPermission($account, 'access sticky_sharrre_bar'); } /** * {@inheritdoc} */ public function defaultConfiguration() { return array( 'label_display' => FALSE, 'providers' => array( 'googlePlus' => 'googlePlus', 'facebook' => 'facebook', 'twitter' => 'twitter', 'linkedin' => 'linkedin', ), 'use_module_css' => 1, 'use_custom_css_selector' => '', 'use_google_analytics_tracking' => 1, ); } /** * {@inheritdoc} */ public function blockForm($form, FormStateInterface $form_state) { $config = \Drupal::config('sticky_sharrre_bar.settings'); $form['block_sticky_sharrre_bar'] = array( '#type' => 'fieldset', '#title' => $this->t('Sticky Sharrre Bar settings'), ); $form['block_sticky_sharrre_bar']['providers'] = array( '#type' => 'checkboxes', '#options' => $config->get('providers_list'), '#title' => $this->t('Main share providers'), '#description' => $this->t('Choose which providers you want to show in this block instance.'), '#default_value' => $this->configuration['providers'], ); // To add the field only if "google_analytics' is enabled. if (\Drupal::moduleHandler()->moduleExists('google_analytics')) { $form['block_sticky_sharrre_bar']['use_google_analytics_tracking'] = array( '#type' => 'checkbox', '#title' => $this->t('Allows tracking social interaction with "Google Analytics".'), '#description' => $this->t('For more details see the :url.', array( ':url' => Link::fromTextAndUrl($this->t('"Sharrre" documentation'), Url::fromUri('http://sharrre.com/track-social.html', array('attributes' => array('target' => '_blank')))) ->toString(), ) ), '#default_value' => $this->configuration['use_google_analytics_tracking'], ); } $form['block_sticky_sharrre_bar']['use_module_css'] = array( '#type' => 'checkbox', '#title' => $this->t('Use the css of the module.'), '#description' => $this->t('Disable if you want override the styles in your theme.'), '#default_value' => $this->configuration['use_module_css'], ); $form['block_sticky_sharrre_bar']['use_custom_css_selector'] = array( '#type' => 'textfield', '#title' => $this->t('Custom CSS selector'), '#size' => 60, '#description' => $this->t('In some cases, module can not find the right region selector in your theme. You can manually set it. Examples: "#navbar", ".header". Is empty by default.'), '#default_value' => $this->configuration['use_custom_css_selector'], ); return $form; } /** * Overrides \Drupal\block\BlockBase::blockSubmit(). */ public function blockSubmit($form, FormStateInterface $form_state) { $values = $form_state->getValue('block_sticky_sharrre_bar'); $this->configuration['providers'] = $values['providers']; $this->configuration['use_module_css'] = $values['use_module_css']; $this->configuration['use_custom_css_selector'] = $values['use_custom_css_selector']; $this->configuration['providers'] = $values['providers']; if (isset($values['use_google_analytics_tracking'])) { $this->configuration['use_google_analytics_tracking'] = $values['use_google_analytics_tracking']; } } /** * Implements \Drupal\block\BlockBase::blockBuild(). * * {@inheritdoc} */ public function build() { $build = array(); $enabled_providers = array(); foreach ($this->configuration['providers'] as $key => $provider) { if ($provider != '0') { $enabled_providers[$key] = $provider; } } if (!empty($enabled_providers)) { $request = \Drupal::request(); $route_match = \Drupal::routeMatch(); $title = \Drupal::service('title_resolver') ->getTitle($request, $route_match->getRouteObject()); if ($title == '') { $title = \Drupal::config('system.site')->get('name'); } // FIXME: need load block info and get id of region. $instance = Block::load('stickysharrrebar'); $region = $instance->get('region'); $custom_css_selector = $this->configuration['use_custom_css_selector']; $js_variables = array( 'providers' => $enabled_providers, 'useGoogleAnalyticsTracking' => $this->configuration['use_google_analytics_tracking'], 'blockRegion' => ($custom_css_selector != '') ? $custom_css_selector : $region, // TODO: fix region. 'isCustomSelector' => ($custom_css_selector != '') ? TRUE : FALSE, ); $build['content'] = array( '#theme' => 'sticky_sharrre_bar_block', '#providers' => $enabled_providers, '#url' => \Drupal::request()->getUri(), '#title' => Html::escape($title), '#attached' => array( 'drupalSettings' => ['stickySharrreBar' => $js_variables], 'library' => array(), ), ); // Add and initialise plugins. $build['content']['#attached']['library'][] = 'sticky_sharrre_bar/jquery-waypoints'; $build['content']['#attached']['library'][] = 'sticky_sharrre_bar/sharrre'; $build['content']['#attached']['library'][] = 'sticky_sharrre_bar/sticky_sharrre_bar_js'; // Very important place. We should cache the result by URL and Language, // because a node title can be the same for all pages. $build['content']['#cache'] = array( 'contexts' => array('url', 'languages'), ); if ($this->configuration['use_module_css'] == 1) { $build['content']['#attached']['library'][] = 'sticky_sharrre_bar/sticky_sharrre_bar_css'; } } return $build; } }
File
js\sticky_sharrre_bar.js
/** * @file * Sticky Sharrre Bar UI. */ (function ($, Drupal, drupalSettings) { 'use strict'; $.exists = function (selector) { return ($(selector).length > 0); }; /** * Attaches the Sticky Sharrre Bar behavior to each block element. */ Drupal.behaviors.stickySharrreBarRender = { attach: function (context) { var enableTracking = (drupalSettings.googleanalytics && drupalSettings.stickySharrreBar.useGoogleAnalyticsTracking === 1) ? true : false, blockRegion = drupalSettings.stickySharrreBar.blockRegion, isCustomSelector = drupalSettings.stickySharrreBar.isCustomSelector, selector = '', $block = $('.block-sticky-sharrre-bar', context); if (isCustomSelector) { selector = blockRegion; } else { // Try to find class, id or html tag of region. if ($.exists('.' + blockRegion)) { selector = '.' + blockRegion + ':first'; } else if ($.exists('#' + blockRegion)) { selector = '#' + blockRegion; } else if ($.exists(blockRegion)) { selector = blockRegion + ':first'; } else { return; } } // Attach the Waypoint and Sticky libraries to the selector. // Move the output code after selector. new Waypoint.Sticky({ element: $block.insertAfter(selector).find('.sticky_sharrre_bar') }); // The "sharrre" plugin requires this object. var buttons = { googlePlus: {}, facebook: {}, twitter: {}, linkedin: {}, digg: {}, delicious: {}, stumbleupon: {}, pinterest: {}, tumblr: {} // TODO: Available from v2.0.0 in "Sharrre" plugin. }; $.each(drupalSettings.stickySharrreBar.providers, function (provider) { if (provider) { var currentProvider = {}; currentProvider[provider] = true; $('#' + provider, context).sharrre({ share: currentProvider, template: '<a class="share ' + provider + '" href="#">' + Drupal.t('Share on <span class="provider_name">!provider</span>', {'!provider': provider}, {}) + '</a></div><span class="count"><a href="#">{total}</a></span>', enableHover: false, enableTracking: enableTracking, enableCounter: true, buttons: buttons, urlCurl: (provider === 'stumbleupon' || provider === 'googlePlus') ? '/sharrre' : '', click: function (api, options) { api.simulateClick(); api.openPopup(provider); } }); } }); } }; })(jQuery, Drupal, drupalSettings);
Conclusion
You can see the full version of the module on https://www.drupal.org/project/sticky_sharrre_bar, in this article just example without SASS, CSS, etc.
Comments