ultimate_table_field-1.0.0-alpha5/src/Element/UltimateTable.php
src/Element/UltimateTable.php
<?php
namespace Drupal\ultimate_table_field\Element;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element\FormElement;
use Drupal\ultimate_table_field\UltimateTableCellFieldManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
// phpcs:disable
/**
* Provides ultimate table custom form element.
*
* @FormElement("ultimate_table")
*/
// @phpstan-ignore-next-line
class UltimateTable extends FormElement implements ContainerFactoryPluginInterface {
use UltimateTableTrait;
/**
* Ultimate table cell field manager plugin.
*
* @var \Drupal\ultimate_table_field\UltimateTableCellFieldManager
*/
protected $ultimateTableCellFieldManager;
/**
* {@inheritDoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, UltimateTableCellFieldManager $ultimateTableCellFieldManager) {
// @phpstan-ignore-next-line
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->ultimateTableCellFieldManager = $ultimateTableCellFieldManager;
}
// phpcs:enable
/**
* {@inheritDoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.ultimate_table_cell_field'),
);
}
/**
* {@inheritDoc}
*/
public function getInfo() {
$class = get_class($this);
return [
'#input' => TRUE,
'#process' => [
[$class, 'elementProcess'],
],
'#element_validate' => [
[$class, 'elementValidate'],
],
'#theme_wrappers' => ['container'],
'#allowed_types' => [],
];
}
/**
* Ultimate table form element process callback.
*/
public static function elementProcess(array &$element, FormStateInterface $form_state, array &$complete_form) {
$parents_reverse = array_reverse($element['#array_parents'] ?? $element['#parents']);
$first_parent = array_pop($parents_reverse);
$id = trim('ut-' . str_replace(['_', '[', ']'], '-', $element['#name'] ?? 'ultimate-table'), '-');
$formatted_id = str_replace('-', '_', $id);
$values_key = $formatted_id . '_values';
$row_count_key = $formatted_id . '_row_count';
$column_count_key = $formatted_id . '_column_count';
if (!$form_state->get($values_key)) {
$form_state->set($values_key, $element['#default_value']['values'] ?? []);
}
$values = $form_state->get($values_key) ?? [];
if (!$form_state->get($row_count_key)) {
$form_state->set($row_count_key, count($values['rows'] ?? []) + 1);
}
if (!$form_state->get($column_count_key)) {
$form_state->set($column_count_key, count($values['columns'] ?? []) ?: 1);
}
$allowed_types = $element['#allowed_types'] ?? [];
$row_count = $form_state->get($row_count_key);
$column_count = $form_state->get($column_count_key);
$element += [
'#input' => TRUE,
'#prefix' => '<div id="' . $id . '"><div id="' . $id . '-data-dialog-wrapper"></div>',
'#suffix' => '</div>',
'#tree' => TRUE,
];
$label = $element['#title'] ?? '';
$required = $element['#required'] ?? FALSE;
$element['label'] = [
'#type' => 'markup',
'#markup' => !empty($label) ? '<strong class="form-item__label ' . ($required ? 'js-form-required form-required' : '') . '">' . $label . '</strong>' : '',
];
$element['nb_unit'] = [
'#type' => 'number',
'#default_value' => 1,
'#description' => t('Number of columns or rows to add'),
'#min' => 1,
'#prefix' => '<div class="table-actions-container"><div class="table-expand-btn">+</div><div class="table-actions">',
];
$element['add_column'] = [
'#type' => 'submit',
'#name' => 'add_column::' . str_replace('-', '_', $id),
'#value' => t('Add column'),
'#submit' => [[self::class, 'elementSubmitCallback']],
'#limit_validation_errors' => [[$first_parent]],
'#ajax' => [
'callback' => [self::class, 'updateTable'],
'wrapper' => $id,
],
];
$element['add_row'] = [
'#type' => 'submit',
'#name' => 'add_row::' . str_replace('-', '_', $id),
'#value' => t('Add row'),
'#submit' => [[self::class, 'elementSubmitCallback']],
'#limit_validation_errors' => [[$first_parent]],
'#ajax' => [
'callback' => [self::class, 'updateTable'],
'wrapper' => $id,
],
'#suffix' => '</div></div>',
];
$element['table'] = [
'#type' => 'table',
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => 'table-order-weight',
],
],
'#attributes' => [
'class' => ['custom-bordered-table'],
],
'#input' => TRUE,
'#tree' => TRUE,
'#prefix' => '<div class="top-scroll"><div class="top-scroll-content"></div></div><div class="table-responsive">',
'#suffix' => '</div>',
];
$ut_cell_field_manager = \Drupal::service('plugin.manager.ultimate_table_cell_field');
for ($i = 0; $i < $row_count; $i++) {
$items_data = NULL;
// Create draggable rows.
if ($i !== 0) {
$element['table'][$i] = [
'#attributes' => ['class' => ['draggable']],
'#weight' => $i,
];
}
for ($j = 0; $j < $column_count; $j++) {
$first_row_ever = FALSE;
if ($i == 0 && $j != 0) {
$first_row_ever = TRUE;
$items_data = $values['columns'][$j] ?? [];
// Remove column item.
$element['table'][$i][$j]['remove_col'] = static::getControlledItem($id, $first_parent, $i, $j, $values, 'column');
}
elseif ($j == 0 && $i != 0) {
// Remove row item.
$element['table'][$i][$j]['remove_row'] = static::getControlledItem($id, $first_parent, $i, $j, $values);
}
elseif ($j == 0 && $i == 0) {
$first_row_ever = TRUE;
$items_data = $values['columns'][$j] ?? [];
}
if (!$first_row_ever) {
$items_data = $values['rows'][$i - 1][$j] ?? [];
}
$cell_id = $id . '_items_data_' . $i . '_' . $j;
$element['table'][$i][$j]['data'] = [
'#type' => 'hidden',
'#default_value' => Json::encode($items_data),
'#attributes' => [
'id' => 'data-input-' . $i . '-' . $j . '--' . $id,
],
];
static::buildCellSummaryElement($id, $first_parent, $element, $i, $j, $items_data, $cell_id, $ut_cell_field_manager, $allowed_types);
}
$element['table'][$i]['weight'] = [
'#type' => 'weight',
'#title' => t('Weight for row @number', ['@number' => $i]),
'#title_display' => 'invisible',
'#default_value' => $i,
'#attributes' => ['class' => ['table-order-weight']],
];
}
$element['update'] = [
'#type' => 'submit',
'#name' => 'update_table::' . str_replace('-', '_', $id),
'#value' => t('Update'),
'#limit_validation_errors' => [[$first_parent]],
'#attributes' => [
'class' => ['js-hide'],
],
'#submit' => [[self::class, 'elementSubmitCallback']],
'#ajax' => [
'callback' => [self::class, 'updateTable'],
'wrapper' => $id,
],
];
// Add legend field if enabled.
if (!empty($element['#enable_legend'])) {
$element['legend'] = [
'#type' => 'text_format',
'#title' => t('Legend'),
'#format' => 'full_html',
'#default_value' => $values['legend'] ?? '',
'#rows' => 3,
];
}
$element['#attached']['library'][] = 'ultimate_table_field/style';
$element['#attached']['library'][] = 'ultimate_table_field/table-actions';
$element['#attached']['library'][] = 'core/drupal.dialog.ajax';
return $element;
}
/**
* {@inheritDoc}
*/
public static function elementValidate(array &$element, FormStateInterface $form_state, array &$complete_form) {
}
/**
* {@inheritDoc}
*/
public static function elementSubmitCallback(array &$element, FormStateInterface $form_state) {
$user_input = $form_state->getUserInput();
$values = $form_state->getValues();
$triggering_element = $form_state->getTriggeringElement();
$name = $triggering_element['#name'] ?? '';
$name_pieces = explode('::', $name);
$name_from_id = $name_pieces[0] ?? NULL;
$id = $name_pieces[1] ?? NULL;
$name = $name_from_id ?? $name;
$is_update_table = FALSE;
$values_key = $id . '_values';
$row_count_key = $id . '_row_count';
$column_count_key = $id . '_column_count';
if (str_starts_with($name, 'row_remove') || str_starts_with($name, 'column_remove')) {
$parents = $triggering_element['#parents'];
array_splice($parents, -5);
}
$main_buttons = [
'update_table',
'add_row',
'add_column',
];
if (in_array($name, $main_buttons, TRUE)) {
$parents = $triggering_element['#parents'];
array_pop($parents);
}
$table_values = NestedArray::getValue($values, $parents);
$nb_unit = !empty($table_values['nb_unit']) ? $table_values['nb_unit'] : 1;
NestedArray::setValue($user_input, [...$parents, 'nb_unit'], 1);
$table_values = $table_values['values'] ?? [];
if (str_starts_with($name, 'add_row')) {
$row_count = $form_state->get($row_count_key);
$form_state->set($row_count_key, $row_count + $nb_unit);
$is_update_table = TRUE;
}
if (str_starts_with($name, 'add_column')) {
$column_count = $form_state->get($column_count_key);
$form_state->set($column_count_key, $column_count + $nb_unit);
$is_update_table = TRUE;
}
if (str_starts_with($name, 'row_remove')) {
$indexes_infos = array_slice($triggering_element['#array_parents'], -4);
$row_index = $indexes_infos[0];
unset($table_values['rows'][$row_index - 1]);
$table_values['rows'] = array_values($table_values['rows']);
$row_count = $form_state->get('row_count');
$form_state->set($row_count_key, --$row_count);
$parents = $triggering_element['#parents'];
$parents = array_slice($parents, 0, count($parents) - 3);
$user_input_row_index = array_pop($parents);
$rows_table_input = NestedArray::getValue($user_input, $parents) ?? [];
unset($rows_table_input[$user_input_row_index]);
NestedArray::setValue($user_input, $parents, $rows_table_input);
}
if (str_starts_with($name, 'column_remove')) {
$indexes_infos = array_slice($triggering_element['#array_parents'], -3);
$column_index = $indexes_infos[0];
unset($table_values['columns'][$column_index]);
$table_values['columns'] = array_values($table_values['columns']);
foreach ($table_values['rows'] as &$value) {
unset($value[$column_index]);
$value = array_values($value);
}
$column_count = $form_state->get('column_count');
$form_state->set($column_count_key, --$column_count);
$parents = $triggering_element['#parents'];
$input_indexes_infos = array_slice($parents, -3);
$input_column_index = reset($input_indexes_infos);
$parents = array_slice($parents, 0, count($parents) - 4);
$rows_table_input = NestedArray::getValue($user_input, $parents) ?? [];
foreach ($rows_table_input as &$row) {
foreach ($row as $key => $item) {
if ($key == $input_column_index) {
unset($row[$key]);
break;
}
}
}
NestedArray::setValue($user_input, $parents, array_values($rows_table_input));
}
if ($is_update_table || str_starts_with($name, 'update_table')) {
$parents = $triggering_element['#parents'];
array_pop($parents);
$parents[] = 'table';
$table_input = NestedArray::getValue($user_input, $parents) ?? [];
}
if (str_starts_with($name, 'row_remove') || str_starts_with($name, 'column_remove')) {
$parents = $triggering_element['#parents'];
$parents = array_splice($parents, -3);
$table_input = NestedArray::getValue($user_input, $parents) ?? [];
}
usort($table_input, function ($a, $b) {
return $a['weight'] <=> $b['weight'];
});
NestedArray::setValue($user_input, $parents, $table_input);
$form_state->setUserInput($user_input);
$form_state->set($values_key, $table_values);
$form_state->setRebuild();
}
/**
* {@inheritDoc}
*/
public static function updateTable(array &$element, FormStateInterface $form_state) {
$user_input = $form_state->getUserInput();
$triggering_element = $form_state->getTriggeringElement();
$name = $triggering_element['#name'];
$name_pieces = explode('::', $name);
$name_from_id = $name_pieces[0] ?? NULL;
$id = $name_pieces[1] ?? NULL;
$formatted_id = str_replace('_', '-', $id);
$name = $name_from_id ?? $name;
if (str_starts_with($name, 'item_remove')) {
$parents = $triggering_element['#parents'];
array_splice($parents, -3);
$data_input = NestedArray::getValue($user_input, $parents);
$data = $data_input['data'] ?? NULL;
$name_pieces = explode(':', $name);
$indexes = explode('_', $name_pieces[1]);
$i = $indexes[0];
$j = $indexes[1];
$k = $indexes[2];
$items_data = $data ? Json::decode($data) : [];
unset($items_data[$k]);
$items_data = array_values($items_data);
$response = new AjaxResponse();
$update_button_selector = 'input[name="update_table::' . $id . '"]';
$data_input_selector = '#data-input-' . $i . '-' . $j . '--' . $formatted_id;
$response->addCommand(new InvokeCommand($data_input_selector, 'val', [Json::encode($items_data)]))
->addCommand(new InvokeCommand($update_button_selector, 'trigger', ['mousedown']));
return $response;
}
$parents = $triggering_element['#array_parents'];
if (str_starts_with($name, 'row_remove') || str_starts_with($name, 'column_remove')) {
array_splice($parents, -5);
}
$main_buttons = [
'update_table',
'add_row',
'add_column',
];
if (in_array($name, $main_buttons, TRUE)) {
array_pop($parents);
}
$element = NestedArray::getValue($element, $parents);
return $element;
}
/**
* {@inheritDoc}
*/
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
$columns_values = [];
$rows_values = [];
$legend = '';
if ($input) {
if (isset($input['legend'])) {
$legend = $input['legend']['value'];
}
$input = $input['table'] ?? $input;
$columns = $input[0];
unset($input[0]);
foreach ($columns as $key => $column) {
if ($key === 'weight') {
continue;
}
if (is_array($column)) {
$data = $column['data'];
$data = Json::decode($data);
$columns_values[] = $data;
}
else {
$columns_values[] = $column;
}
}
if (!empty($input)) {
foreach ($input as $row) {
$row_values = [];
foreach ($row as $column_index => $value) {
if ($column_index === 'weight') {
continue;
}
if (is_array($value)) {
$data = $value['data'];
$data = Json::decode($data);
$row_values[] = $data;
}
else {
$row_values[] = $value;
}
}
$rows_values[] = $row_values;
}
}
}
$values = [
'values' => [
'columns' => $columns_values,
'rows' => $rows_values,
'legend' => $legend,
],
];
return $values;
}
}
