jumper-8.x-1.1/src/Plugin/Block/JumperBlock.php
src/Plugin/Block/JumperBlock.php
<?php
namespace Drupal\jumper\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an 'Jumper' block.
*
* @Block(
* id = "jumper_block",
* admin_label = @Translation("Jumper"),
* category = @Translation("Jumper"),
* )
*/
class JumperBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
$instance = new static($configuration, $plugin_id, $plugin_definition);
$instance->moduleHandler = $container->get('module_handler');
return $instance;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'target' => '#header',
'duration' => 1000,
'offset' => 0,
'icon' => '',
'color' => 'blue',
'no_text' => TRUE,
'text' => 'Top',
'style' => 'round-2',
'visibility' => 600,
'selectors' => '',
'autovlm' => FALSE,
'ootb' => FALSE,
];
}
/**
* Overrides \Drupal\block\BlockBase::blockForm().
*/
public function blockForm($form, FormStateInterface $form_state) {
$path = \blazy()->getPath('module', 'jumper');
$readme = $this->moduleHandler->moduleExists('help')
? Url::fromRoute('help.page', ['name' => 'jumper'])->toString()
: Url::fromUri('base:' . $path . '/README.md')->toString();
$form['ootb'] = [
'#type' => 'checkbox',
'#title' => $this->t('Out of the block'),
'#description' => $this->t('Check to put the jumper outside the block as direct body child, useful if having display issues with some theme or module due to fixed positioning, etc. In case you theme it with <code>.block</code> selector, be sure to update your CSS, since it is no longer inside <code>.block</code>. Use <code>.jumper--block</code>. etc. directly instead.'),
];
$form['target'] = [
'#type' => 'textfield',
'#title' => $this->t('Target'),
'#description' => $this->t('A single valid CSS selector as the scrolling target which must exist at the topmost of your page template, e.g.: <code>#page, #main, #top, .header</code>, etc. Only one! Note if #header is fixed position, such as with off-canvas layout, consider another target element at the TOP most of your page.'),
'#max_length' => 255,
];
$form['duration'] = [
'#type' => 'textfield',
'#title' => $this->t('Duration'),
'#description' => $this->t('Pass the time the `jump()` takes, in milliseconds.'),
'#field_suffix' => '<abbr title="Milliseconds">ms</abbr>',
'#max_length' => 32,
];
$form['offset'] = [
'#type' => 'textfield',
'#title' => $this->t('Offset'),
'#description' => $this->t('Offset a `jump()`, _only if to an element_, by a number of pixels. Use minus sign (-) to stop before the top of the element, else positive number to stop afterward. If having a fixed header, play around with this.'),
'#field_suffix' => '<abbr title="Pixel">px</abbr>',
'#max_length' => 32,
];
$form['icon'] = [
'#type' => 'textfield',
'#title' => $this->t('Icon class'),
'#description' => $this->t('Custom icon class, font-awesome, fontello, etc. E.g.: <code>fa far fa-angle-up</code> or <code>icon icon-angle-up</code>, depending on vendors.'),
'#max_length' => 64,
];
$form['color'] = [
'#type' => 'select',
'#title' => $this->t('Background color'),
'#options' => [
'grey' => $this->t('Grey'),
'dark' => $this->t('Dark'),
'purple' => $this->t('Purple'),
'orange' => $this->t('Orange'),
'blue' => $this->t('Blue'),
'lime' => $this->t('Lime'),
'red' => $this->t('Red'),
],
'#empty_option' => $this->t('- None -'),
'#description' => $this->t('Choose the provided background color, or leave empty to DIY.'),
];
$form['no_text'] = [
'#type' => 'checkbox',
'#title' => $this->t('Visually hide the title text'),
'#description' => $this->t('If checked, use the icon only. If unchecked, be sure to adjust the styling accordingly with text presence.'),
];
$form['text'] = [
'#type' => 'textfield',
'#title' => $this->t('Text'),
'#description' => $this->t('Text to display if not hidden. You can include HTML with the only allowed tag `span` for advanced positioning/ styling, e.g.: <br /><strong><span>Top</span></strong><br />Further theming is required if you have more texts than just `Top`. Leave it empty to not have text. To be compatible with custom icon class, wrap it with SPAN.'),
'#max_length' => 60,
'#states' => [
'visible' => [':input[name="settings[no_text]"]' => ['checked' => FALSE]],
],
];
$form['style'] = [
'#type' => 'select',
'#title' => $this->t('Style'),
'#options' => [
'round' => $this->t('Round'),
'round-2' => $this->t('Round 2'),
'round-8' => $this->t('Round 8'),
'round-12' => $this->t('Round 12'),
],
'#empty_option' => $this->t('Square'),
'#description' => $this->t('Choose the provided style, or leave it to default Square to DIY.'),
];
$form['visibility'] = [
'#type' => 'textfield',
'#title' => $this->t('Activation point'),
'#description' => $this->t('The jumper will be visible when reaching this point. This is not accurate as it is debounced every 250ms, affected by windows scrolling speed.'),
'#field_suffix' => '<abbr title="Pixel">px</abbr>',
'#max_length' => 32,
];
$form['selectors'] = [
'#type' => 'textfield',
'#title' => $this->t('Additional selectors'),
'#description' => $this->t('Additional selectors to act as jumpers. Use comma separated valid CSS selectors supported by <strong>querySelectorAll</strong>, e.g.:<br /> <strong>.menu-item a[href*="#"], .button--cta</strong><br />Be specific to avoid unintentional link hijacking such as with Bootstrap tabs. This block MUST be present where those selectors are. See <a href=":url">README</a> for relevant info.', [':url' => $readme]),
'#max_length' => 255,
];
foreach (array_keys($this->defaultConfiguration()) as $key) {
$form[$key]['#default_value'] = $this->configuration[$key];
}
return $form;
}
/**
* Overrides \Drupal\block\BlockBase::blockSubmit().
*/
public function blockSubmit($form, FormStateInterface $form_state) {
foreach ($this->defaultConfiguration() as $key => $default) {
$this->setConfigurationValue($key, $form_state->getValue($key));
}
}
/**
* {@inheritdoc}
*/
public function build() {
$config = $this->getConfiguration();
$target = trim(strip_tags($config['target'] ?? ''));
$fragment = strpos($target, '#') !== FALSE ? str_replace('#', '', $target) : ' ';
$text = strip_tags($config['text'], '<span>');
$build = [
'#type' => 'link',
'#title' => [
'#markup' => trim($text),
'#allowed_tags' => ['span'],
],
'#url' => Url::fromRoute('<current>'),
'#options' => [
'attributes' => [
'class' => ['jumper', 'jumper--block'],
'data-target' => is_numeric($target) ? (int) $target : $target,
'id' => 'jumper',
],
'fragment' => $fragment,
'external' => TRUE,
'html' => TRUE,
],
'#attached' => [
'library' => ['jumper/load'],
'drupalSettings' => ['jumper' => $config],
],
];
$classes = [];
if (!empty($config['color'])) {
$classes[] = 'jumper--color jumper--' . $config['color'];
}
if (!empty($config['style'])) {
$classes[] = 'jumper--' . $config['style'];
}
if (!empty($config['no_text'])) {
$classes[] = 'jumper--no-text';
}
if (!empty($config['icon'])) {
$classes[] = $config['icon'];
$classes[] = 'is-iconized';
}
if (!empty($config['ootb'])) {
$classes[] = 'jumper--ootb';
}
if ($classes) {
foreach ($classes as $class) {
$build['#options']['attributes']['class'][] = $class;
}
}
return $build;
}
}
