You are here

public static function ChartDataCollectorTable::processDataCollectorTable in Charts 8.4

Same name and namespace in other branches
  1. 5.0.x src/Element/ChartDataCollectorTable.php \Drupal\charts\Element\ChartDataCollectorTable::processDataCollectorTable()

Processes the element to render a table to collect a data for the chart.

Parameters

array $element: The element.

\Drupal\Core\Form\FormStateInterface $form_state: The form state.

array $complete_form: The complete form.

Return value

array The processed element.

File

src/Element/ChartDataCollectorTable.php, line 66

Class

ChartDataCollectorTable
Provides a chart data collector table form element.

Namespace

Drupal\charts\Element

Code

public static function processDataCollectorTable(array &$element, FormStateInterface $form_state, array &$complete_form) {
  $parents = $element['#parents'];
  $id_prefix = implode('-', $parents);
  $wrapper_id = Html::getUniqueId($id_prefix . '-ajax-wrapper');
  $value = $element['#value'];
  $required = !empty($element['#required']);
  $user_input = $form_state
    ->getUserInput();
  $element_state = self::getElementState($parents, $form_state);

  // Getting columns and rows count.
  if (empty($element_state['data_collector_table']) || empty($element_state['table_categories_identifier'])) {
    $identifier_value = $value['table_categories_identifier'] ?? self::FIRST_COLUMN;
    $element_state['table_categories_identifier'] = $identifier_value;
    $element_state['data_collector_table'] = $value['data_collector_table'] ?? [];
    $element_state['data_collector_table'] = $element_state['data_collector_table'] ?: self::initializeEmptyTable($element, $identifier_value);
    self::setElementState($parents, $form_state, $element_state);
  }
  else {

    // This is hack to make ajax call retun the proper identifier.
    $element_state['table_categories_identifier'] = $value['table_categories_identifier'];
  }

  // Enforce tree.
  $element = [
    '#tree' => TRUE,
    '#prefix' => '<div id="' . $wrapper_id . '">',
    '#suffix' => '</div>',
    // Pass the id along to other methods.
    '#wrapper_id' => $wrapper_id,
  ] + $element;
  $element['table_categories_identifier'] = [
    '#type' => 'radios',
    '#title' => t('Categories are identified by'),
    '#options' => [
      self::FIRST_COLUMN => t('First column'),
      self::FIRST_ROW => t('First row'),
    ],
    '#description' => t('Select whether the first row or column hold the categories data'),
    '#required' => $required,
    '#default_value' => $element_state['table_categories_identifier'],
    '#ajax' => [
      'callback' => [
        get_called_class(),
        'ajaxRefresh',
      ],
      'progress' => [
        'type' => 'throbber',
      ],
      'wrapper' => $wrapper_id,
      'effect' => 'fade',
    ],
  ];
  $table = [
    '#type' => 'table',
    '#tree' => TRUE,
    '#header' => [],
    '#responsive' => FALSE,
    '#attributes' => [
      'class' => 'data-collector-table',
    ],
  ];
  $table_drag = $element['#table_drag'];
  $table_drag_group = Html::cleanCssIdentifier($id_prefix . '-order-weight');
  if ($table_drag) {
    $table['#tabledrag'] = [
      [
        'action' => 'order',
        'relationship' => 'sibling',
        'group' => $table_drag_group,
      ],
    ];
  }
  if ($element['#table_wrapper'] && $element['#table_wrapper'] === 'container') {
    $element['table_wrapper'] = [
      '#type' => 'container',
      '#attributes' => $element['#table_wrapper_attributes'],
      '#tree' => FALSE,
    ];
    $element['table_wrapper']['data_collector_table'] =& $table;
  }
  else {
    $element['data_collector_table'] =& $table;
  }
  $rows = count($element_state['data_collector_table']);

  // Make the weight list always reflect the current number of values.
  $max_weight = count($element_state['data_collector_table']);
  $max_row = max(array_keys($element_state['data_collector_table']));

  // The first column need to be for colors.
  $is_first_column = $element_state['table_categories_identifier'] === self::FIRST_COLUMN;
  $first_row_key = NULL;
  foreach ($element_state['data_collector_table'] as $i => $row) {
    $first_row_key = $first_row_key === NULL ? $i : $first_row_key;
    $table_first_row = $i === $first_row_key;
    $add_color_first_row = $is_first_column && $table_first_row;
    $first_col_key = NULL;
    $row_form =& $table[$i];
    $row_form['#attributes']['class'][] = 'data-collector-table--row';

    // Adding the row textfield cells.
    foreach ($row as $j => $column) {
      if ($j === 'weight') {
        continue;
      }
      $first_col_key = $first_col_key === NULL ? $j : $first_col_key;
      $table_first_col = $j === $first_col_key;

      // To be used to skip color input on cell[0][0].
      $is_category_cell = $table_first_col && $table_first_row;
      $row_form[$j]['data'] = [
        '#type' => 'textfield',
        '#title' => t('Data for column @col - Row @row', [
          '@row' => $i,
          '@col' => $j,
        ]),
        '#title_display' => 'invisible',
        '#size' => 10,
        '#default_value' => is_array($column) ? $column['data'] : $column,
        '#wrapper_attributes' => [
          'class' => [
            'data-collector-table--row--cell',
          ],
        ],
      ];
      if (!$is_category_cell && ($add_color_first_row || !$is_first_column && $j === $first_col_key)) {
        $row_form[$j]['#wrapper_attributes'] = [
          'class' => [
            'container-inline',
          ],
        ];
        $row_form[$j]['color'] = [
          '#type' => 'textfield',
          '#title' => t('Color'),
          '#title_display' => 'invisible',
          '#attributes' => [
            'TYPE' => 'color',
          ],
          '#size' => 10,
          '#maxlength' => 7,
          '#default_value' => $column['color'] ?? ChartsDefaultColors::randomColor(),
        ];
      }
    }

    // Adding weight if table drag enabled.
    if ($table_drag) {
      $row_form['#attributes']['class'][] = 'draggable';
      if ($i + 1 === $rows) {
        $default_weight = $max_weight;
      }
      else {
        $default_weight = $max_row + 1;
      }
      $row_form['weight'] = [
        '#type' => 'weight',
        '#title' => t('Weight'),
        '#title_display' => 'invisible',
        '#delta' => $max_weight,
        '#default_value' => $element_state['data_collector_table'][$i]['weight'] ?? $default_weight,
        '#attributes' => [
          'class' => [
            $table_drag_group,
          ],
        ],
      ];

      // Used by SortArray::sortByWeightProperty to sort the rows.
      if (isset($user_input['data_collector_table'][$i])) {
        $input_weight = $user_input['data_collector_table'][$i]['weight'];

        // Make sure the weight is not out of bounds due to removals.
        if ($user_input['data_collector_table'][$i]['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 delete button.
    $row_form['delete'] = self::buildOperationButton('delete', 'row', $id_prefix, $wrapper_id, $i, [], [
      'class' => [
        'data-collector-table--row--delete',
      ],
    ]);
  }
  $colspan = 1;
  if ($table_drag) {

    // Sort the values by weight. Ensures weight is preserved on ajax refresh.
    uasort($table, [
      '\\Drupal\\Component\\Utility\\SortArray',
      'sortByWeightProperty',
    ]);

    // Increasing colspan when weight column is added.
    $colspan = 2;
  }

  // Building the column delete button.
  $table['_delete_column_buttons'] = [
    '#attributes' => [
      'class' => [
        'data-collector-table--column-deletes-row',
      ],
    ],
  ];

  // Using first row to get the count of columns.
  $first_row = current($element_state['data_collector_table']);

  // Using array filter to exclude weight key when grabbing the row columns.
  $columns = self::excludeWeightColumnFromRow($first_row);
  $max_column = max(array_keys($first_row));
  foreach ($columns as $column) {
    $table['_delete_column_buttons'][$column] = self::buildOperationButton('delete', 'column', $id_prefix, $wrapper_id, $column, [], [
      'class' => [
        'data-collector-table--column--delete',
      ],
    ]);
    if ($column === $max_column) {
      $table['_delete_column_buttons'][$column]['#wrapper_attributes']['colspan'] = $colspan;
    }
  }

  // Empty Column under delete operation placeholder.
  $table['_delete_column_buttons'][$max_column + 1] = [
    '#markup' => '',
  ];

  // Footer operations.
  $table['_operations'] = [
    '#attributes' => [
      'class' => [
        'data-collector-table--oprations-row',
      ],
    ],
  ];
  $table['_operations']['wrapper'] = [
    '#type' => 'container',
    '#wrapper_attributes' => [
      'colspan' => count($columns) + $colspan,
    ],
  ];
  $table['_operations']['wrapper']['add_column'] = self::buildOperationButton('add', 'column', $id_prefix, $wrapper_id, NULL);
  $table['_operations']['wrapper']['add_row'] = self::buildOperationButton('add', 'row', $id_prefix, $wrapper_id, NULL);
  if ($element['#import_csv']) {
    $element['import'] = [
      '#type' => 'details',
      '#title' => t('Import Data from CSV'),
      '#description' => t('Note importing data from CSV will overwrite all the current data entry in the table.'),
      '#open' => FALSE,
    ];
    $element['import']['csv'] = [
      '#name' => 'files[' . $id_prefix . ']',
      '#title' => t('File upload'),
      '#title_display' => 'invisible',
      '#type' => 'file',
      '#upload_validators' => [
        'file_validate_extensions' => [
          'csv',
        ],
        'file_validate_size' => [
          Environment::getUploadMaxSize(),
        ],
      ],
    ];
    $element['import']['upload'] = [
      '#type' => 'submit',
      '#value' => t('Upload CSV'),
      '#name' => $id_prefix . '-import-csv',
      '#attributes' => [
        'class' => [
          Html::cleanCssIdentifier($id_prefix . '--import-csv'),
        ],
      ],
      '#submit' => [
        [
          get_called_class(),
          'importCsvToTableSubmit',
        ],
      ],
      '#limit_validation_errors' => [
        array_merge($parents, [
          'import',
          'csv',
        ]),
        array_merge($parents, [
          'import',
          'upload',
        ]),
      ],
      '#ajax' => [
        'callback' => [
          get_called_class(),
          'ajaxRefresh',
        ],
        'progress' => [
          'type' => 'throbber',
        ],
        'wrapper' => $wrapper_id,
        'effect' => 'fade',
      ],
      '#operation' => 'csv',
      '#csv_separator' => $element['#import_csv_separator'] ?? ',',
    ];
  }
  return $element;
}