Last active
January 22, 2020 00:59
-
-
Save nikathone/94b6041de604c1a5d979e58653e68a2e to your computer and use it in GitHub Desktop.
Drupal table with ajax submit button in the header
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\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; | |
} | |
} |
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
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