media_library_extended-1.x-dev/src/Form/PaneSelectForm.php
src/Form/PaneSelectForm.php
<?php
namespace Drupal\media_library_extended\Form;
use Drupal\Core\Ajax\CloseDialogCommand;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Pager\PagerManagerInterface;
use Drupal\Core\Pager\PagerParametersInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
use Drupal\media_library\MediaLibraryState;
use Drupal\media_library\OpenerResolverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Pane Select Form class.
*
* @package Drupal\media_library_extended\Form
*/
class PaneSelectForm extends FormBase {
/**
* The currently active request object.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The media library opener resolver service.
*
* @var \Drupal\media_library\OpenerResolverInterface
*/
protected $openerResolver;
/**
* The media library pane the form is being built for.
*
* @var \Drupal\media_library_extended\Entity\MediaLibraryPaneInterface
*/
protected $pane;
/**
* The source plugin corresponding to the active media library pane.
*
* @var \Drupal\media_library_extended\Plugin\MediaLibrarySourceInterface
*/
protected $plugin;
/**
* The current page being rendered.
*
* @var int
*/
protected $page;
/**
* The total result cound provided by the source plugin.
*
* @var int
*/
protected $resultCount;
/**
* The pager manager.
*
* @var \Drupal\Core\Pager\PagerManagerInterface
*/
protected $pagerManager;
/**
* The pager parameters.
*
* @var \Drupal\Core\Pager\PagerParametersInterface
*/
protected $pagerParameters;
/**
* {@inheritdoc}
*/
public function __construct(RequestStack $request_stack, OpenerResolverInterface $opener_resolver, PagerManagerInterface $pager_manager, PagerParametersInterface $pager_parameters) {
$this->request = $request_stack->getCurrentRequest();
$this->openerResolver = $opener_resolver;
$this->pagerManager = $pager_manager;
$this->pagerParameters = $pager_parameters;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('request_stack'),
$container->get('media_library.opener_resolver'),
$container->get('pager.manager'),
$container->get('pager.parameters')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'media_library_extended_pane_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// @todo Remove in https://www.drupal.org/project/drupal/issues/2504115
// Currently the default URL for all AJAX form elements is the current URL,
// not the form action. This causes bugs when this form is rendered from an
// AJAX path like /views/ajax, which cannot process AJAX form submits.
$query = $this->request->query->all();
$query[FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;
// @todo Cleanly read target bundle from MediaLibraryState.
$this->target_bundle = $query['media_library_selected_type'];
$this->pane = $form_state->get('pane');
$this->plugin = $form_state->get('source_plugin');
$this->addFilters($form, $form_state, $query);
$this->addPreviews($form, $form_state, $query);
$this->addPager($form, $form_state, $query);
$form['#theme'] = 'media_library_pane';
$form['#attributes']['class'][] = 'media-library-view';
$form['#attributes']['data-element-id'] = 'media_library_select_form';
$form['#attached']['library'][] = 'media_library_extend/ui';
// Add 'Insert selected' button.
$form['actions']['submit'] = [
'#type' => 'submit',
'#ajax' => [
'url' => Url::fromRoute('media_library.ui'),
'options' => [
'query' => $query,
],
'callback' => '::ajaxSubmitForm',
],
'#value' => $this->t('Insert selected'),
'#button_type' => 'primary',
'#field_id' => 'form_selection',
'#attributes' => [
'class' => [
'media-library-select',
],
// By default, the AJAX system tries to move the focus back to the
// element that triggered the AJAX request. Since the media library is
// closed after clicking the select button, the focus can't be moved
// back. We need to set the 'data-disable-refocus' attribute to prevent
// the AJAX system from moving focus to a random element. The select
// button triggers an update in the opener, and the opener should be
// responsible for moving the focus. An example of this can be seen in
// MediaLibraryWidget::updateWidget().
// @see \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget::updateWidget()
'data-disable-refocus' => 'true',
],
];
return $form;
}
/**
* Adds plugin filters to the pane's form.
*
* @param array $form
* The current form to modify.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
* @param array $query
* Current query parameters for link generation.
*/
protected function addFilters(array &$form, FormStateInterface $form_state, array $query) {
// Offer filtering and search.
$form['#parents'] = [];
$form['filters'] = [
'#parents' => ['filters'],
'#type' => 'fieldset',
'#title' => $this->t('Filters'),
'#tree' => TRUE,
];
$form['filters']['actions'] = [
// @todo Should by type 'actions'.
'#type' => 'container',
'#weight' => 100,
];
$form['filters']['actions']['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Apply'),
'#attributes' => [
'class' => [
'media-library-extend-form-submit',
],
],
'#ajax' => [
'url' => Url::fromRoute('media_library.ui'),
'options' => [
'query' => [
'page' => 0,
FormBuilderInterface::AJAX_FORM_REQUEST => TRUE,
] + $query,
],
'callback' => '::ajaxFilterForm',
'wrapper' => 'media-library-extend-form-content',
],
];
$subform_state = SubformState::createForSubform($form['filters'], $form, $form_state);
$form['filters'] = $this->plugin->buildform($form['filters'], $subform_state);
$elements = Element::children($form['filters']);
if ($elements === ['actions']) {
// Actions element is likely present, but useless without filters.
// Do not show empty filter container.
// We do not deny access, because Javascript needs to be able to use
// the filter button as an input element.
$form['filters']['#attributes']['class'][] = 'visually-hidden';
}
// Set initial values from form state.
$this->setPluginState($form, $form_state);
$items_per_page = $this->getItemsPerPage();
// Add current page after determining result count.
$this->resultCount = $this->plugin->getCount();
if (!is_null($this->resultCount)) {
$pager = $this->pagerManager->createPager($this->resultCount, $items_per_page);
$this->page = $pager->getCurrentPage();
}
else {
// @todo Give countless plugins a way to say that there are no more
// results left.
// Get current page.
$requested_page = $this->pagerParameters->findPage();
// Set number of items high enough to allow another page to be shown.
$pager = $this->pagerManager->createPager(($requested_page + 1) * $items_per_page + 1, $items_per_page);
$this->page = $pager->getCurrentPage();
}
// Update plugin state with pager info.
$this->plugin->setValue('page', $this->page);
// @todo Validate and submit callbacks.
}
/**
* Determines the number of items to show on each page.
*
* @return int
* The configured number of items per page.
*/
protected function getItemsPerPage() {
if ($this->plugin->isConfigurable() && isset($this->plugin->getConfiguration()['items_per_page'])) {
return $this->plugin->getConfiguration()['items_per_page'];
}
return 20;
}
/**
* Adds item previews to the pane's form.
*
* @param array $form
* The current form to modify.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
* @param array $query
* Current query parameters for link generation.
*/
protected function addPreviews(array &$form, FormStateInterface $form_state, array $query) {
$form['content'] = [
'#theme' => 'media_library_pane_content',
'#attributes' => [
'class' => ['js-media-library-view-content'],
'id' => 'media-library-extend-form-content',
],
];
// @todo Allow plugin to specify a header message.
// Add result count.
$form['content']['result_count'] = [
'#type' => 'item',
'#title' => $this->formatPlural($this->resultCount,
'1 items matches your search',
'@count items match your search.'
),
'#attributes' => [
'class' => ['view-header'],
],
];
if (is_null($this->resultCount)) {
$form['content']['result_count']['#access'] = FALSE;
}
// Display result previews.
$results = $this->plugin->getResults();
foreach ($results as $result) {
// @todo We will need different templates for other view modes.
$form['content']['previews'][] = [
'#theme' => 'media_library_item__small',
'#attributes' => [
'class' => [
'js-media-library-item',
'js-click-to-select',
],
],
'select' => [
'#type' => 'container',
'#attributes' => [
'class' => [
'js-click-to-select-checkbox',
],
],
'select_checkbox' => [
'#type' => 'checkbox',
'#title' => $this->t('Select @name', ['@name' => $result['label']]),
'#title_display' => 'invisible',
'#return_value' => 'mle:' . $this->pane->id() . ':' . $result['id'],
// The checkbox's value is never processed by this form. It is
// present for usability and accessibility reasons, and only used by
// JavaScript to track whether or not this media item is selected.
// The hidden 'current_selection' field is used to store the actual
// IDs of selected media items.
'#value' => FALSE,
],
],
'rendered_entity' => [
'#theme' => 'media_library_result_preview',
'#result' => $result,
],
];
}
// The selection is persistent across different pages in the media library
// and populated via JavaScript.
// @see \Drupal\media_library\Plugin\views\field\MediaLibrarySelectForm
$form['content']['form_selection'] = [
'#type' => 'hidden',
'#attributes' => [
// This is used to identify the hidden field in the form via JavaScript.
'id' => 'media-library-modal-selection',
'name' => 'form_selection',
],
];
}
/**
* Adds a pager to the pane's form.
*
* @param array $form
* The current form to modify.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
* @param array $query
* Current query parameters for link generation.
*/
protected function addPager(array &$form, FormStateInterface $form_state, array $query) {
// Render the pager.
$form['content']['pager'] = [
'#type' => 'pager',
'#parameters' => $query,
'#route_name' => 'media_library.ui',
'#theme_wrappers' => [
'container' => [
'#attributes' => [
'class' => ['js-media-library-extend-pager'],
],
],
],
];
if (is_null($this->resultCount)) {
// Media library depends on views anyway, so we can hijack its mini pager.
$form['content']['pager']['#theme'] = 'views_mini_pager';
unset($form['content']['pager']['#type']);
}
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
_media_library_extend_form_insert_validate($form, $form_state);
parent::validateForm($form, $form_state);
}
/**
* Stores the current plugin's state.
*
* @param array $form
* The current form to modify.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current form state.
*/
protected function setPluginState(array $form, FormStateInterface $form_state) {
$subform_state = SubformState::createForSubform($form['filters'], $form, $form_state);
$values = $subform_state->getValues();
$values['page'] = $this->page;
$values['target_bundle'] = $this->target_bundle;
$this->plugin->setValues($values);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// This space intentionally left blank.
}
/**
* {@inheritdoc}
*/
public function ajaxSubmitForm(array &$form, FormStateInterface $form_state, Request $request) {
$field_id = $form_state->getTriggeringElement()['#field_id'];
$selected_ids = explode(',', $form_state->getValue($field_id));
// Allow the opener service to handle the selection.
$state = MediaLibraryState::fromRequest($request);
return $this->openerResolver->get($state)
->getSelectionResponse($state, $selected_ids)
->addCommand(new CloseDialogCommand());
}
/**
* {@inheritdoc}
*/
public function ajaxFilterForm(array &$form, FormStateInterface $form_state, Request $request) {
$query = $request->query->all();
$query[FormBuilderInterface::AJAX_FORM_REQUEST] = TRUE;
$query['query'] = $form_state->getValue('query');
$this->addFilters($form, $form_state, $query);
$this->addPreviews($form, $form_state, $query);
$this->addPager($form, $form_state, $query);
return $form['content'];
}
}
