Created
August 24, 2020 11:04
-
-
Save hudri/4cb05bd631ef53fa28ca80341f73b8da to your computer and use it in GitHub Desktop.
Drupal Form with multiple submit buttons in a table
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Drupal\wt_pricetable\Form; | |
use Drupal\Core\Form\FormBase; | |
use Drupal\Core\Form\FormStateInterface; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
use Drupal\Core\Entity\EntityTypeManagerInterface; | |
use Drupal\paragraphs\Entity\Paragraph; | |
use Drupal\Component\Render\FormattableMarkup; | |
use Drupal\Core\Url; | |
use Drupal\wt_svgicons\SvgIconsHelperService; | |
/** | |
* Provides a bulk edit form for pricetable paragraphs accross mulitple wt_room nodes | |
*/ | |
class BulkEditForm extends FormBase { | |
/** | |
* @var EntityTypeManagerInterface $entityTypeManager | |
*/ | |
protected $entityTypeManager; | |
/** | |
* @var SvgIconsHelperService $iconHelper | |
*/ | |
protected $iconHelper; | |
/** | |
* Class constructor. | |
*/ | |
public function __construct(EntityTypeManagerInterface $entityTypeManager, SvgIconsHelperService $iconHelper) { | |
$this->entityTypeManager = $entityTypeManager; | |
$this->iconHelper = $iconHelper; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public static function create(ContainerInterface $container) { | |
return new static( | |
$container->get('entity_type.manager'), | |
$container->get('wt_svgicons.helper') | |
); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getFormId() { | |
return 'wt_pricetable_bulk_edit'; | |
} | |
public function cbSubmitCopy(array &$form, FormStateInterface $form_state) { | |
$form_state->set('num_pricerows', $form_state->get('num_pricerows') + 1); | |
$form_state->setRebuild(); | |
} | |
public function cbCopyRow(array &$form, FormStateInterface $form_state) { | |
// using Ajax seems pointless, but necessary due Drupal bug | |
// @see https://www.drupal.org/project/drupal/issues/2546700#comment-10801702 | |
return $form['pt']; | |
} | |
public function cbSubmitAdd(array &$form, FormStateInterface $form_state) { | |
$form_state->set('num_pricerows', $form_state->get('num_pricerows') + 1); | |
$form_state->setRebuild(); | |
} | |
public function cbAddRow(array &$form, FormStateInterface $form_state) { | |
// using Ajax seems pointless, but necessary due Drupal bug | |
// @see https://www.drupal.org/project/drupal/issues/2546700#comment-10801702 | |
return $form['pt']; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function buildForm(array $form, FormStateInterface $form_state) { | |
$form['#attached']['library'][] = 'wt_pricetable/admin'; | |
$query = $this->entityTypeManager->getStorage('node')->getQuery(); | |
$query->condition('type', 'wt_room'); | |
$query->sort('field_weight', 'ASC'); | |
$query->sort('title', 'ASC', \Drupal::languageManager()->getDefaultLanguage()->getId()); | |
$query->sort('nid', 'ASC'); | |
$roomIds = $query->execute(); | |
$rooms = $this->entityTypeManager->getStorage('node')->loadMultiple($roomIds); | |
if (count($roomIds) < 1) { | |
$this->messenger()->addWarning($this->t('You cannot edit prices because there are no rooms. <a href=":link">Create a room here.</a>', [':link' => Url::fromRoute('node.add', ['node_type' => 'wt_room'])->toString()])); | |
return $form; | |
} | |
$query = $this->entityTypeManager->getStorage('taxonomy_term')->getQuery(); | |
$query->condition('vid', 'rate'); | |
$query->sort('weight', 'ASC'); | |
$rateIds = $query->execute(); | |
$rates = $this->entityTypeManager->getStorage('taxonomy_term')->loadMultiple($rateIds); | |
if (count($rateIds) == 0) { | |
$this->messenger()->addWarning($this->t('You cannot edit prices because there are no rates. <a href=":link">Create a rate here.</a>', [':link' => Url::fromRoute('entity.taxonomy_term.add_form', ['taxonomy_vocabulary' => 'rate'])->toString()])); | |
return $form; | |
} | |
$query = $this->entityTypeManager->getStorage('paragraph')->getQuery(); | |
$query->condition('type', 'pricetable'); | |
$query->condition('parent_id', array_keys($rooms), 'IN'); | |
$query->sort('field_pt_from', 'ASC'); | |
$query->sort('field_pt_to', 'ASC'); | |
$query->sort('field_pt_rate', 'ASC'); | |
$priceIds = $query->execute(); | |
$prices = $this->entityTypeManager->getStorage('paragraph')->loadMultiple($priceIds); | |
$initialDefaultPrices = []; | |
foreach ($prices as $price) { | |
$key = $price->get('field_pt_from')->value . '___' . $price->get('field_pt_to')->value . '___' . $price->get('field_pt_rate')->target_id; | |
if (!array_key_exists($key, $initialDefaultPrices)) { | |
$initialDefaultPrices[$key] = [ | |
'from' => $price->get('field_pt_from')->value, | |
'to' => $price->get('field_pt_to')->value, | |
'rate' => $price->get('field_pt_rate')->target_id | |
]; | |
} | |
$initialDefaultPrices[$key]['room-'.$price->get('parent_id')->value] = $price->get('field_pt_price')->value; | |
} | |
$initialDefaultPrices = array_values($initialDefaultPrices); | |
$form['#tree'] = TRUE; | |
$form['intro'] = [ | |
'#markup' => '<p><ul>' . | |
'<li>' . $this->t('The combination of <em>From</em>, <em>To</em> and <em>Rate</em> must be unique.') . '</li>' . | |
'<li>' . $this->t('<del>Strikethrough rows</del> will be deleted after clicking the button <em>Update all prices</em>.') . '</li>' . | |
'</ul></p>' | |
]; | |
$form['pt'] = [ | |
'#type' => 'container', | |
'#prefix' => '<div id="pt-ajax-wrapper">', | |
'#suffix' => '</div>', | |
]; | |
$form['pt']['table'] = [ | |
'#type' => 'container', | |
'#prefix' => '<table class="pt">', | |
'#suffix' => '</table>', | |
]; | |
$th = '<tr class="pt__head">' . | |
'<th class="text-align-center">' . $this->iconHelper->getSvgIcon('trash', 'far') . '</th>' . | |
'<th class="text-align-center">' . $this->iconHelper->getSvgIcon('copy', 'far') . '</i></th>' . | |
'<th>' . $this->t('From', [], ['context' => 'time']) . '</th>' . | |
'<th>' . $this->t('To', [], ['context' => 'time']) . '</th>'. | |
'<th>' . $this->t('Rate', [], ['context' => 'price']) . '</th>'; | |
foreach ($rooms as $room) { | |
$th .= '<th>' . $room->get('title')->value . '</th>'; | |
} | |
$form['pt']['table']['head'] = [ | |
'#type' => 'inline_template', | |
'#template' => '<thead><tr>' . $th . '</tr></thead>', | |
]; | |
if ($form_state->get('num_pricerows') === NULL) { | |
$form_state->set('num_pricerows', count($initialDefaultPrices)); | |
} | |
$copySource = false; | |
if (isset($form_state->getTriggeringElement()['#parents'][4]) && $form_state->getTriggeringElement()['#parents'][4] == 'copy') { | |
$copySource = $form_state->getTriggeringElement()['#parents'][3]; | |
} | |
$form['pt']['table']['rows'] = [ | |
'#type' => 'container', | |
'#prefix' => '<tbody>', | |
'#suffix' => '</tbody>', | |
]; | |
for ($i=0; $i<$form_state->get('num_pricerows'); $i++) { | |
$form['pt']['table']['rows'][$i] = [ | |
'#type' => 'container', | |
'#prefix' => '<tr class="pt__row pt__row--'. $i .'">', | |
'#suffix' => '</tr>', | |
]; | |
$form['pt']['table']['rows'][$i]['delete'] = [ | |
'#type' => 'checkbox', | |
'#title' => $this->t('Delete row'), | |
'#title_display' => 'invisible', | |
'#prefix' => '<td class="text-align-center">', | |
'#suffix' => '</td>', | |
]; | |
$form['pt']['table']['rows'][$i]['copy'] = [ | |
'#type' => 'submit', | |
'#value' => html_entity_decode(''), | |
'#copy_source' => $i, | |
'#name' => 'pt[table][rows]['.$i.'][copy]', // @see https://www.drupal.org/node/2546700 - must be unqiue for $form_state->getTriggeringElement() | |
'#submit' => ['::cbSubmitCopy'], | |
'#ajax' => [ // @see https://www.drupal.org/project/drupal/issues/2546700#comment-10801702 - must use Ajax for $form_state->getTriggeringElement() | |
'callback' => '::cbCopyRow', | |
'wrapper' => 'pt-ajax-wrapper', | |
], | |
'#attributes' => ['class' => ['far']], | |
'#prefix' => '<td class="text-align-center">', | |
'#suffix' => '</td>', | |
'#use_button_template' => true, | |
'#children' => [ | |
'#type' => 'inline_template', | |
'#template' => $this->iconHelper->getSvgIcon('copy', 'far') | |
] | |
]; | |
$form['pt']['table']['rows'][$i]['from'] = [ | |
'#type' => 'date', | |
'#title' => $this->t('From', [], ['context' => 'time']), | |
'#title_display' => 'invisible', | |
'#prefix' => '<td>', | |
'#suffix' => '</td>', | |
'#default_value' => is_numeric($copySource) ? $form_state->getValue(['pt', 'table', 'rows', $copySource, 'from']) : @$initialDefaultPrices[$i]['from'], | |
]; | |
$form['pt']['table']['rows'][$i]['to'] = [ | |
'#type' => 'date', | |
'#title' => $this->t('To', [], ['context' => 'time']), | |
'#title_display' => 'invisible', | |
'#prefix' => '<td>', | |
'#suffix' => '</td>', | |
'#default_value' => is_numeric($copySource) ? $form_state->getValue(['pt', 'table', 'rows', $copySource, 'to']) : @$initialDefaultPrices[$i]['to'], | |
]; | |
$selOptions = []; | |
foreach ($rates as $id => $rate) { | |
$selOptions[$id] = $rate->getName(); | |
} | |
$form['pt']['table']['rows'][$i]['rate'] = [ | |
'#type' => 'select', | |
'#title_display' => 'invisible', | |
'#title' => $this->t('Rate', [], ['context' => 'price']), | |
'#options' => $selOptions, | |
'#prefix' => '<td>', | |
'#suffix' => '</td>', | |
'#default_value' => is_numeric($copySource) ? $form_state->getValue(['pt', 'table', 'rows', $copySource, 'rate']) : @$initialDefaultPrices[$i]['rate'], | |
]; | |
foreach ($rooms as $room) { | |
$form['pt']['table']['rows'][$i]['room-' . $room->id()] = [ | |
'#type' => 'number', | |
'#title_display' => 'invisible', | |
'#title' => $this->t('Price'), | |
'#min' => 0, | |
'#step' => 0.01, | |
'#prefix' => '<td>', | |
'#suffix' => '</td>', | |
'#default_value' => is_numeric($copySource) ? $form_state->getValue(['pt', 'table', 'rows', $copySource, 'room-'.$room->id()]) : @$initialDefaultPrices[$i]['room-'.$room->id()], | |
]; | |
} | |
}; | |
$form['add_pricerow'] = [ | |
'#type' => 'submit', | |
'#value' => t('Add empty row'), | |
'#submit' => ['::cbSubmitAdd'], | |
'#ajax' => [ | |
'callback' => '::cbAddRow', | |
'wrapper' => 'pt-ajax-wrapper', | |
], | |
]; | |
$form['submit'] = [ | |
'#value' => t('Update all prices'), | |
'#type' => 'submit', | |
'#attributes' => ['class' => ['button', 'button--primary']], | |
]; | |
return $form; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function validateForm(array &$form, FormStateInterface $form_state) { | |
$uniqueActiveRows = []; | |
foreach (($form_state->getValue(['pt', 'table', 'rows']) ?? []) as $i => $row) { | |
$isEmptyRow = TRUE; | |
foreach ($form_state->getValue(['pt', 'table', 'rows', $i]) as $key => $value) { | |
if (!in_array($key, ['delete', 'rate', 'copy'])) { | |
$isEmptyRow = $isEmptyRow && !$value; | |
} | |
} | |
if (!$isEmptyRow && $form_state->getValue(['pt', 'table', 'rows', $i, 'delete']) != "1") { | |
foreach ($form_state->getValue(['pt', 'table', 'rows', $i]) as $key => $value) { | |
if ($key != 'delete' && $value == "") { | |
$form_state->setErrorByName('pt][table][rows]['.$i.']['.$key, $this->t('Required field')); | |
} | |
} | |
if ($form_state->getValue(['pt', 'table', 'rows', $i, 'from']) >= $form_state->getValue(['pt', 'table', 'rows', $i, 'to'])) { | |
$form_state->setErrorByName('pt][table][rows]['.$i.'][to', $this->t('<em>To</em> must always be greater than <em>From</em>')); | |
} | |
if ($form_state->getValue(['pt', 'table', 'rows', $i, 'from']) && $form_state->getValue(['pt', 'table', 'rows', $i, 'to']) && $form_state->getValue(['pt', 'table', 'rows', $i, 'rate'])) { | |
$uniqueKey = $form_state->getValue(['pt', 'table', 'rows', $i, 'from']) .'___'. $form_state->getValue(['pt', 'table', 'rows', $i, 'to']) .'___'. $form_state->getValue(['pt', 'table', 'rows', $i, 'rate']); | |
if (in_array($uniqueKey, $uniqueActiveRows)) { | |
$form_state->setErrorByName('pt][table][rows]['.$i.'][from', $this->t('Row @ROWNUMBER_1 is a duplicate of row @ROWNUMBER_2', ['@ROWNUMBER_1' => $i+1, '@ROWNUMBER_2' => array_search($uniqueKey, $uniqueActiveRows)+1])); | |
$form_state->setErrorByName('pt][table][rows]['.$i.'][to', $this->t('Row @ROWNUMBER_1 is a duplicate of row @ROWNUMBER_2', ['@ROWNUMBER_1' => $i+1, '@ROWNUMBER_2' => array_search($uniqueKey, $uniqueActiveRows)+1])); | |
$form_state->setErrorByName('pt][table][rows]['.$i.'][rate', $this->t('Row @ROWNUMBER_1 is a duplicate of row @ROWNUMBER_2', ['@ROWNUMBER_1' => $i+1, '@ROWNUMBER_2' => array_search($uniqueKey, $uniqueActiveRows)+1])); | |
} | |
else { | |
$uniqueActiveRows[$i] = $uniqueKey; | |
} | |
} | |
} | |
} | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function submitForm(array &$form, FormStateInterface $form_state) { | |
$query = $this->entityTypeManager->getStorage('node')->getQuery(); | |
$query->condition('type', 'wt_room'); | |
$query->sort('title', 'ASC'); | |
$roomIds = $query->execute(); | |
$rooms = $this->entityTypeManager->getStorage('node')->loadMultiple($roomIds); | |
$query = $this->entityTypeManager->getStorage('taxonomy_term')->getQuery(); | |
$query->condition('vid', 'rate'); | |
$query->sort('weight', 'ASC'); | |
$rateIds = $query->execute(); | |
$rates = $this->entityTypeManager->getStorage('taxonomy_term')->loadMultiple($rateIds); | |
$ignoreMinprice = []; | |
foreach ($rates as $rate) { | |
$ignoreMinprice[$rate->id()] = $rate->get('field_ignore_minprice')->value ?? '0'; | |
} | |
$query = $this->entityTypeManager->getStorage('paragraph')->getQuery(); | |
$query->condition('type', 'pricetable'); | |
$query->condition('parent_id', array_keys($rooms), 'IN'); | |
$priceIds = $query->execute(); | |
$prices = $this->entityTypeManager->getStorage('paragraph')->loadMultiple($priceIds); | |
$this->entityTypeManager->getStorage('paragraph')->delete($prices); | |
$today = date('Y-m-d'); | |
foreach ($rooms as $room) { | |
$priceHostField=[]; | |
$minPrice = NULL; | |
foreach ($form_state->getValue(['pt', 'table', 'rows']) as $i => $row) { | |
$from = $form_state->getValue(['pt', 'table', 'rows', $i, 'from']); | |
$to = $form_state->getValue(['pt', 'table', 'rows', $i, 'to']); | |
$rate = $form_state->getValue(['pt', 'table', 'rows', $i, 'rate']); | |
$price = $form_state->getValue(['pt', 'table', 'rows', $i, 'room-'.$room->id()]); | |
if ( | |
$form_state->getValue(['pt', 'table', 'rows', $i, 'delete']) != "1" && | |
$from && $to && is_numeric($price) && $rate | |
) { | |
$paragraph = Paragraph::create([ | |
'type' => 'pricetable', | |
'field_pt_from' => $from, | |
'field_pt_to' => $to, | |
'field_pt_rate' => ['target_id' => $rate], | |
'field_pt_price' => $price, | |
]); | |
$paragraph->save(); | |
if ($ignoreMinprice[$rate] != 1 && $to >= $today && $price > 0) { | |
if (is_null($minPrice) || $price < $minPrice) | |
$minPrice = $price; | |
} | |
$priceHostField[] = [ | |
'target_id' => $paragraph->id(), | |
'target_revision_id' => $paragraph->getRevisionId(), | |
]; | |
} | |
} | |
if (\Drupal::configFactory()->getEditable('wt_pricetable.settings')->get('update_minprice') == 1) { | |
$room->set('field_minprice', $minPrice); | |
} | |
$room->set('field_pricetable', $priceHostField); | |
$room->save(); | |
} | |
if (\Drupal::configFactory()->getEditable('wt_pricetable.settings')->get('update_minprice') == 1) { | |
$this->messenger()->addStatus($this->t('All prices tables updated and <em>from price</em> in all rooms adjusted.')); | |
} | |
else { | |
$this->messenger()->addStatus($this->t('All prices updated.')); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment