Skip to content

Instantly share code, notes, and snippets.

@nikathone
Last active January 22, 2020 00:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikathone/94b6041de604c1a5d979e58653e68a2e to your computer and use it in GitHub Desktop.
Save nikathone/94b6041de604c1a5d979e58653e68a2e to your computer and use it in GitHub Desktop.
Drupal table with ajax submit button in the header
<?php
namespace Drupal\hello_world\Form;
use Drupal\Component\Utility\Environment;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class DynamicDataTableForm extends FormBase {
const DATA_TABLE_WRAPPER_ID = 'dynamic-data-table-wrapper';
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'hello_world_dynamic_data_table_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$user_input = $form_state->getUserInput();
$form['intro'] = [
'#markup' => $this->t('<p>Let get started</p>'),
];
$form['table_categories_id'] = [
'#type' => 'radios',
'#title' => $this->t('Categories are indentified by'),
'#options' => [
'first_row' => $this->t('First row'),
'first_column' => $this->t('First column'),
],
'#description' => $this->t('Select whether the first row or column hold the categories data'),
];
$wrapper_id = self::DATA_TABLE_WRAPPER_ID;
$order_group = 'dynamic-data-table-order-weight';
$form['data_table'] = [
'#type' => 'table',
'#header' => [],
'#responsive' => FALSE,
'#tabledrag' => [
[
'action' => 'order',
'relationship' => 'sibling',
'group' => $order_group,
],
],
'#prefix' => '<div id="' . $wrapper_id . '">',
'#suffix' => '</div>',
];
$columns = $form_state->get('data_table_columns');
if (empty($columns)) {
$columns = ['_new'];
$form_state->set('data_table_columns', $columns);
}
// Adding operations column.
$columns_count = count($columns);
// Build rows.
$rows = (array) $form_state->get('data_table_rows');
if (empty($rows)) {
$rows = ['_new'];
$form_state->set('data_table_rows', $rows);
}
// Make the weight list always reflect the current number of rows.
$max_weight = count($rows);
foreach ($rows as $row_index => $id) {
$row_form = &$form['data_table'][$row_index];
// The tabledrag element is always added to the first cell in the row,
// so we add an empty cell to guide it there, for better styling.
$row_form['#attributes']['class'][] = 'draggable';
// Adding texfield based on the number of columns
foreach ($columns as $col_index => $column) {
$data_key = 'data_col_' . $col_index . '_row_' . $row_index;
$row_form[$data_key] = [
'#type' => 'textfield',
'#title' => $this->t('Data for column @col_index - Row @index', [
'@index' => $row_index,
'@col_index' => $col_index,
]),
'#title_display' => 'invisible',
'#size' => 10,
'#required' => TRUE,
];
}
if ($id === '_new') {
$default_weight = $max_weight;
}
$row_form['weight'] = [
'#type' => 'weight',
'#title' => $this->t('Weight'),
'#title_display' => 'invisible',
'#delta' => $max_weight,
'#default_value' => $default_weight,
'#attributes' => [
'class' => [$order_group],
],
];
// Used by SortArray::sortByWeightProperty to sort the rows.
if (isset($user_input['data_table'][$row_index])) {
$input_weight = $user_input['data_table'][$row_index]['weight'];
// Make sure the weight is not out of bounds due to removals.
if ($user_input['data_table'][$row_index]['weight'] > $max_weight) {
$input_weight = $max_weight;
}
// Reflect the updated user input on the element.
$row_form['weight']['#value'] = $input_weight;
$row_form['#weight'] = $input_weight;
}
else {
$row_form['#weight'] = $default_weight;
}
$row_form['delete'] = $this->buildOperationButton('delete', 'row', $row_index, TRUE);
}
// Sort the values by weight. Ensures weight is preserved on ajax refresh.
uasort($form['data_table'], ['\Drupal\Component\Utility\SortArray', 'sortByWeightProperty']);
// Building the column delete.
$form['data_table'][$row_index + 1] = [
'#tree' => FALSE,
];
foreach ($columns as $col_index => $column) {
$form['data_table'][$row_index + 1][$col_index] = $this->buildOperationButton('delete', 'column', $col_index, TRUE);
if (($col_index + 1) == $columns_count) {
$form['data_table'][$row_index + 1][$col_index]['#wrapper_attributes']['colspan'] = 2;
}
}
// Column under delete operation placeholder
$form['data_table'][$row_index + 1][$col_index + 1] = [
'#markup' => '',
];
$form['data_table']['_operations'] = [
'#tree' => FALSE,
];
$form['data_table']['_operations']['wrapper'] = [
'#type' => 'container',
'#wrapper_attributes' => [
'colspan' => $columns_count + 2,
],
];
$form['data_table']['_operations']['wrapper']['add_column'] = $this->buildOperationButton('add', 'column');
$form['data_table']['_operations']['wrapper']['add_row'] = $this->buildOperationButton('add', 'row');
$form['import'] = [
'#type' => 'details',
'#title' => t('Import Data from CSV'),
'#description' => $this->t('Note importing data from CSV will overwrite all the current table data.'),
'#open' => FALSE,
];
$form['import']['csv'] = [
'#name' => 'files[data_table]',
'#title' => 'File upload',
'#title_display' => 'invisible',
'#type' => 'file',
'#upload_validators' => [
'file_validate_extensions' => ['csv'],
'file_validate_size' => [Environment::getUploadMaxSize()],
],
];
$form['import']['upload'] = [
'#type' => 'submit',
'#value' => t('Upload CSV'),
'#name' => 'data-table-import-csv',
'#attributes' => [
'class' => ['data-table-import-csv'],
],
'#submit' => ['::importCsvDataTableSubmit'],
'#limit_validation_errors' => [
['import', 'csv'],
['import', 'upload'],
],
'#ajax' => [
'callback' => '::dataTableAjax',
'progress' => ['type' => 'throbber', 'message' => NULL],
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$values = $form_state->getValues();
}
/**
* Ajax callback for buttons operations.
*/
public function dataTableAjax(array $form, FormStateInterface $form_state) {
return $form['data_table'];
}
/**
* Buttons operations submit callback.
*/
public function operationDataTableSubmit(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
$operation_on = 'data_table_' . $triggering_element['#operation_on'] . 's';
$operation = $triggering_element['#operation'];
$data_table = (array) $form_state->get($operation_on);
if ($operation === 'delete') {
$index = $triggering_element['#' . $triggering_element['#operation_on'] . '_index'];
unset($data_table[$index]);
}
else {
$data_table[] = '_new';
}
$form_state->set($operation_on, $data_table);
$form_state->setRebuild();
}
public function importCsvDataTableSubmit(array $form, FormStateInterface $form_state) {
$files = \Drupal::request()->files->get('files');
// $values = $form_state->getValues();
/** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file_upload */
$file_upload = $files['data_table'];
if ($file_upload && $handle = fopen($file_upload->getPathname(), 'r')) {
// Checking the encoding of the CSV file to be UTF-8.
$encoding = 'UTF-8';
if (function_exists('mb_detect_encoding')) {
$file_contents = file_get_contents($file_upload->getPathname());
$encodings = ['UTF-8', 'ISO-8859-1', 'WINDOWS-1251'];
$encodings_list = implode(',', $encodings);
$encoding = mb_detect_encoding($file_contents, $encodings_list);
}
// Populate CSV values.
$data = [];
$max_cols = 0;
$rows_count = 0;
$separator = ',';
while (($csv = fgetcsv($handle, 0, $separator)) != FALSE) {
foreach ($csv as $value) {
$data['table'][$rows_count][] = self::convertEncoding($value, $encoding);
}
$cols = count($csv);
if ($cols > $max_cols) {
$max_cols = $cols;
}
$rows_count++;
}
fclose($handle);
$columns = array_keys($data['table'][0]);
$form_state->set('data_table_columns', $columns);
$rows = array_keys($data['table']);
$form_state->set('data_table_rows', $rows);
$data['counter']['cols'] = $max_cols;
$data['counter']['rows'] = $rows_count;
$form_state->set('data_table_csv', $data);
\Drupal::messenger()
->addMessage(t('Successfully imported @file', ['@file' => $file_upload->getClientOriginalName()]));
}
else {
\Drupal::messenger()
->addError(t('There was a problem importing the provided file data.'));
}
$form_state->setRebuild();
}
/**
* Utility method to build a button render array for the various data table operation.
*/
private function buildOperationButton($operation, $on, $index = NULL, $has_custom_name = FALSE, $access = NULL, $attributes = []) {
$name = $operation . '_' . $on;
$submit = [];
if (!is_null($index)) {
$name .= '_' . $index;
$submit['#' . $on . '_index'] = $index;
}
if (is_bool($access)) {
$submit['#access'] = $access;
}
if ($has_custom_name) {
$submit['#name'] = $name;
}
if ($attributes) {
$submit['#attributes'] = $attributes;
}
$submit += [
'#type' => 'submit',
'#value' => $this->t('@op @on', [
'@op' => ucfirst($operation),
'@on' => $on,
]),
'#limit_validation_errors' => [],
'#submit' => ['::operationDataTableSubmit'],
'#operation' => $operation,
'#operation_on' => $on,
'#ajax' => [
'callback' => '::dataTableAjax',
'wrapper' => self::DATA_TABLE_WRAPPER_ID,
],
];
return $submit;
}
/**
* Helper function to detect and convert strings not in UTF-8 to UTF-8.
*
* @param string $data
* The string which needs converting.
* @param string $encoding
* The encoding of the CSV file.
*
* @return string
* UTF encoded string.
*/
protected static function convertEncoding($data, $encoding) {
// Converting UTF-8 to UTF-8 will not work.
if ($encoding == 'UTF-8') {
return $data;
}
// Try convert the data to UTF-8.
if ($encoded_data = Unicode::convertToUtf8($data, $encoding)) {
return $encoded_data;
}
// Fallback on the input data.
return $data;
}
}
hello_world.dynamic_data_table_form:
path: /hello-world/dynamic-data-table-form
defaults:
_form: Drupal\hello_world\Form\DynamicDataTableForm
_title: 'Dynamic data table form'
requirements:
_permission: 'access content'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment