lory-8.x-1.x-dev/modules/ui/src/Form/LoryForm.php
modules/ui/src/Form/LoryForm.php
<?php
namespace Drupal\lory_ui\Form;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\lory\LoryDefault;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Extends base form for lory instance configuration form.
*/
class LoryForm extends EntityForm {
/**
* The lory admin service.
*
* @var \Drupal\lory\Form\LoryAdminInterface
*/
protected $admin;
/**
* The lory manager service.
*
* @var \Drupal\lory\LoryManagerInterface
*/
protected $manager;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$instance = parent::create($container);
$instance->admin = $container->get('lory.admin');
$instance->manager = $container->get('lory.manager');
return $instance;
}
/**
* Returns the lory admin.
*/
public function admin() {
return $this->admin;
}
/**
* Returns the lory manager.
*/
public function manager() {
return $this->manager;
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
// Change page title for the duplicate operation.
if ($this->operation == 'duplicate') {
$form['#title'] = $this->t('<em>Duplicate lory optionset</em>: @label', ['@label' => $this->entity->label()]);
$this->entity = $this->entity->createDuplicate();
}
// Change page title for the edit operation.
if ($this->operation == 'edit') {
$form['#title'] = $this->t('<em>Edit lory optionset</em>: @label', ['@label' => $this->entity->label()]);
}
$lory = $this->entity;
$tooltip = ['class' => ['is-tooltip']];
$options = $lory->getOptions() ?: [];
$defaults = LoryDefault::jsOptionsetSettings();
$admin_css = $this->manager->configLoad('admin_css', 'blazy.settings');
$form['#attributes']['class'][] = 'form--lory';
$form['#attributes']['class'][] = 'form--slick';
$form['#attributes']['class'][] = 'form--optionset';
if ($admin_css) {
$form['#attached']['library'][] = 'blazy/admin';
}
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#default_value' => $lory->label(),
'#maxlength' => 255,
'#required' => TRUE,
'#description' => $this->t("Label for the Lory optionset."),
'#attributes' => $tooltip,
'#prefix' => '<div class="form__header form__half form__half--first has-tooltip clearfix">',
];
// Keep the legacy CTools ID, i.e.: name as ID.
$form['name'] = [
'#type' => 'machine_name',
'#default_value' => $lory->id(),
'#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
'#machine_name' => [
'source' => ['label'],
'exists' => '\Drupal\lory\Entity\Lory::load',
],
'#attributes' => $tooltip,
'#disabled' => !$lory->isNew(),
'#suffix' => '</div>',
];
$form['group'] = [
'#type' => 'select',
'#title' => $this->t('Group'),
'#options' => [
'main' => $this->t('Main'),
'nav' => $this->t('Nav/ Thumbnail'),
],
'#empty_option' => $this->t('- None -'),
'#default_value' => $lory->getGroup(),
'#description' => $this->t('Group this optionset to avoid confusion for optionset selections. Leave empty to make it available for all.'),
'#access' => $lory->id() != 'default',
'#attributes' => $tooltip,
'#prefix' => '<div class="form__header form__half form__half--last has-tooltip clearfix">',
];
$form['optimized'] = [
'#type' => 'checkbox',
'#title' => $this->t('Optimized'),
'#default_value' => $lory->optimized(),
'#description' => $this->t('Check to optimize the stored options. Anything similar to defaults will not be stored, except those required by sub-modules and theme_lory(). Like you hand-code/ cherry-pick the needed options, and are smart enough to not repeat defaults, and free up memory. The rest are taken care of by JS. Uncheck only if theme_lory() can not satisfy the needs, and more hand-coded preprocess is needed which is less likely in most cases.'),
'#access' => $lory->id() != 'default',
'#attributes' => $tooltip,
'#wrapper_attributes' => ['class' => ['form-item--tooltip-wide']],
'#suffix' => '</div>',
];
// Main JS options.
$form['options'] = [
'#type' => 'container',
'#tree' => TRUE,
'#open' => TRUE,
'#collapsed' => FALSE,
'#attributes' => ['class' => ['details--settings', 'has-tooltip']],
'#description' => $this->t('Lory is a touch enabled minimalistic slider written in vanilla JavaScript. Non-core library features are experimental, not battle-tested, yet. Use them at your own risk!'),
];
$definition['settings'] = $options;
$definition['defaults'] = $defaults;
$this->optionsetForm($form['options'], $definition);
if ($admin_css) {
$form['optimized']['#field_suffix'] = ' ';
$form['optimized']['#title_display'] = 'before';
}
$excludes = ['container', 'details', 'item', 'hidden', 'submit'];
foreach ($defaults as $name => $default) {
if (!isset($form['options'][$name])) {
continue;
}
if ($admin_css) {
if ($form['options'][$name]['#type'] == 'checkbox' && $form['options'][$name]['#type'] != 'checkboxes') {
$form['options'][$name]['#field_suffix'] = ' ';
$form['options'][$name]['#title_display'] = 'before';
}
elseif ($form['options'][$name]['#type'] == 'checkboxes' && !empty($form['options'][$name]['#options'])) {
foreach ($form['options'][$name]['#options'] as $i => $option) {
$form['options'][$name][$i]['#field_suffix'] = ' ';
$form['options'][$name][$i]['#title_display'] = 'before';
}
}
}
if (in_array($form['options'][$name]['#type'], $excludes) || !isset($form['options'][$name])) {
continue;
}
if (!isset($form['options'][$name]['#default_value'])) {
$form['options'][$name]['#default_value'] = (NULL !== $lory->getOption($name)) ? $lory->getOption($name) : $default;
}
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
// Optimized if so configured.
$lory = $this->entity;
$default = $lory->id() == 'default';
$options = $form_state->getValue('options');
// Cast the values.
$this->typecastOptionset($options);
if (!$default && !$form_state->isValueEmpty('optimized')) {
$defaults = $lory::defaultSettings();
$options = array_diff_assoc($options, $defaults);
}
$lory->setOptions($options, FALSE);
}
/**
* Overrides Drupal\Core\Entity\EntityFormController::save().
*/
public function save(array $form, FormStateInterface $form_state) {
parent::save($form, $form_state);
$lory = $this->entity;
// Prevent leading and trailing spaces in lory names.
$lory->set('label', trim($lory->label()));
$lory->set('id', $lory->id());
$status = $lory->save();
$label = $lory->label();
$edit_link = $lory->toLink($this->t('Edit'), 'edit-form')->toString();
$config_prefix = $lory->getEntityType()->getConfigPrefix();
$message = ['@config_prefix' => $config_prefix, '%label' => $label];
$notice = [
'@config_prefix' => $config_prefix,
'%label' => $label,
'link' => $edit_link,
];
if ($status == SAVED_UPDATED) {
// If we edited an existing entity.
// @todo #2278383.
$this->messenger()->addMessage($this->t('@config_prefix %label has been updated.', $message));
$this->logger('lory')->notice('@config_prefix %label has been updated.', $notice);
}
else {
// If we created a new entity.
$this->messenger()->addMessage($this->t('@config_prefix %label has been added.', $message));
$this->logger('lory')->notice('@config_prefix %label has been added.', $notice);
}
$form_state->setRedirectUrl($lory->toUrl('collection'));
}
/**
* Returns the typecast values.
*
* @param array $settings
* An array of Optionset settings.
*/
public function typecastOptionset(array &$settings = []) {
if (empty($settings)) {
return;
}
$lory = $this->entity;
$defaults = $lory::defaultSettings();
foreach ($defaults as $name => $value) {
if (isset($settings[$name])) {
// Seems double is ignored, and causes a missing schema, unlike float.
$type = gettype($defaults[$name]);
$type = $type == 'double' ? 'float' : $type;
settype($settings[$name], $type);
}
}
}
/**
* Returns the closing ending form elements.
*/
public function optionsetForm(array &$form, $definition = []) {
$settings = isset($definition['settings']) ? $definition['settings'] : [];
$defaults = isset($definition['defaults']) ? $definition['defaults'] : [];
$descriptions = $this->getDescriptions();
foreach ($defaults as $key => $value) {
$type = gettype($value);
$form_type = $type == 'boolean' ? 'checkbox' : 'textfield';
if (in_array($key, ['ease'])) {
$form_type = 'select';
}
// @todo: Enable when ready.
if (in_array($key, ['autoWidth', 'vertical'])) {
$form_type = 'hidden';
}
$title = $key;
$form[$key] = [
'#type' => $form_type,
'#title' => $this->t('@title', ['@title' => $title]),
'#default_value' => isset($settings[$key]) ? $settings[$key] : $value,
'#weight' => 100,
'#description' => isset($descriptions[$key]) ? $descriptions[$key] : '',
];
switch ($key) {
case 'arrowDown':
if (!function_exists('jumper_help')) {
$form[$key]['#disabled'] = TRUE;
}
break;
case 'ease':
$form[$key]['#options'] = $this->getCssEasingOptions();
break;
case 'rewindSpeed':
case 'slideSpeed':
case 'snapBackSpeed':
$form[$key]['#field_suffix'] = $this->t('<abbr title="Millisecond">ms</abbr>');
break;
default:
break;
}
}
}
/**
* Returns default classes.
*/
public function getDescriptions() {
$features = $this->manager->assetManager()->getAssets()['features'];
$descriptions = [];
foreach ($features as $key => $feature) {
$hidden = isset($feature['type']) && $feature['type'] == 'hidden';
if ($hidden) {
continue;
}
if (!isset($features[$key]['description'])) {
continue;
}
$option = isset($feature['name']) ? $feature['name'] : $key;
$descriptions[$option] = $features[$key]['description'];
}
return [
'adaptiveHeight' => $this->t('Enable adaptive height for SINGLE slide horizontal carousels.'),
'draggable' => $this->t('Use dragging and touch swiping.'),
'enableMouseEvents' => $this->t('Enabled mouse events aka desktop draggable.'),
'ease' => $this->t('Cubic bezier easing functions: http://easings.net/de.'),
'focusOnSelect' => $this->t('Enable focus on selected element (click). The slide will change when clicked.'),
'infinite' => $this->t('Like carousel, works with multiple slides. (do not combine with rewind). The number of slides to be shown per slide. To have responsive displays, use comma-separated key:value (<strong>viewport : visible slides amount</strong>) pairs, e.g.: <br /><strong>210:1, 767:3, 1023:5</strong> <br />which means 3 items per slide for window greater than 767, etc. At below 767, it will show 1. This is mobile first, or min-width, which require explicit min-width viewport. Works best with odd numbers, Width = Fixed grid, and centerMode.'),
'randomize' => $this->t('Randomize slide orders, useful to rotate ads/product displays within cached pages.'),
'rewind' => $this->t('If slider reached the last slide, with next click the slider goes back to the startindex. (do not combine with infinite).'),
'rewindSpeed' => $this->t('Time in milliseconds for the animation of the rewind after the last slide.'),
'slideSpeed' => $this->t('Time in milliseconds for the animation of a valid slide attempt.'),
'snapBackSpeed' => $this->t('Time for the snapBack of the slider if the slide attempt was not valid.'),
'initialSlide' => $this->t('Index of the starting slide (zero-based).'),
'slidesToScroll' => $this->t('Slides scrolled at once.'),
'autoWidth' => $this->t('WIP! Variable width. (NOT WORKING YET!)'),
'width' => $this->t('Defines width to control individual slide, or slide container width as needed in the format: <strong>WIDTHxHEIGHT</strong>. This is a rare case, used to define slide container, such as 3d container. Do not do this unless supported by the provided 3d skins/effect. You can also define this via CSS to have a better control with media queries, and leave it empty here.'),
] + $descriptions;
}
/**
* List of available CSS easing methods.
*
* @return array
* An array of CSS easings for select options, or all for the mappings.
*
* @see https://github.com/kenwheeler/slick/issues/118
* @see http://matthewlein.com/ceaser/
* @see http://www.w3.org/TR/css3-transitions/
*/
public function getCssEasingOptions() {
return [
// Defaults/ Native.
'ease' => 'ease',
'linear' => 'linear',
'ease-in' => 'ease-in',
'ease-out' => 'ease-out',
'swing' => 'swing|ease-in-out',
// Penner Equations (approximated).
'cubic-bezier(0.550, 0.085, 0.680, 0.530)' => 'easeInQuad',
'cubic-bezier(0.550, 0.055, 0.675, 0.190)' => 'easeInCubic',
'cubic-bezier(0.895, 0.030, 0.685, 0.220)' => 'easeInQuart',
'cubic-bezier(0.755, 0.050, 0.855, 0.060)' => 'easeInQuint',
'cubic-bezier(0.470, 0.000, 0.745, 0.715)' => 'easeInSine',
'cubic-bezier(0.950, 0.050, 0.795, 0.035)' => 'easeInExpo',
'cubic-bezier(0.600, 0.040, 0.980, 0.335)' => 'easeInCirc',
'cubic-bezier(0.600, -0.280, 0.735, 0.045)' => 'easeInBack',
'cubic-bezier(0.250, 0.460, 0.450, 0.940)' => 'easeOutQuad',
'cubic-bezier(0.215, 0.610, 0.355, 1.000)' => 'easeOutCubic',
'cubic-bezier(0.165, 0.840, 0.440, 1.000)' => 'easeOutQuart',
'cubic-bezier(0.230, 1.000, 0.320, 1.000)' => 'easeOutQuint',
'cubic-bezier(0.390, 0.575, 0.565, 1.000)' => 'easeOutSine',
'cubic-bezier(0.190, 1.000, 0.220, 1.000)' => 'easeOutExpo',
'cubic-bezier(0.075, 0.820, 0.165, 1.000)' => 'easeOutCirc',
'cubic-bezier(0.175, 0.885, 0.320, 1.275)' => 'easeOutBack',
'cubic-bezier(0.455, 0.030, 0.515, 0.955)' => 'easeInOutQuad',
'cubic-bezier(0.645, 0.045, 0.355, 1.000)' => 'easeInOutCubic',
'cubic-bezier(0.770, 0.000, 0.175, 1.000)' => 'easeInOutQuart',
'cubic-bezier(0.860, 0.000, 0.070, 1.000)' => 'easeInOutQuint',
'cubic-bezier(0.445, 0.050, 0.550, 0.950)' => 'easeInOutSine',
'cubic-bezier(1.000, 0.000, 0.000, 1.000)' => 'easeInOutExpo',
'cubic-bezier(0.785, 0.135, 0.150, 0.860)' => 'easeInOutCirc',
'cubic-bezier(0.680, -0.550, 0.265, 1.550)' => 'easeInOutBack',
];
}
}
