component_library-1.0.x-dev/src/Form/ComponentLibraryAssetForm.php
src/Form/ComponentLibraryAssetForm.php
<?php
declare(strict_types=1);
namespace Drupal\component_library\Form;
use Drupal\component_library\Entity\ComponentLibraryAsset;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
* The configuration form for component_library_asset entities.
*/
final class ComponentLibraryAssetForm extends EntityForm {
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state): array {
$form = parent::buildForm($form, $form_state);
$form['label'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#maxlength' => 255,
'#default_value' => $this->entity->label(),
'#description' => $this->t('Label for the component_library_asset.'),
'#required' => TRUE,
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $this->entity->id(),
'#machine_name' => [
'exists' => '\Drupal\component_library\Entity\ComponentLibraryAsset::load',
],
'#disabled' => !$this->entity->isNew(),
];
$form['files'] = [
'#type' => 'fieldset',
'#tree' => TRUE,
'#title' => $this->t('Files'),
'#attributes' => ['id' => 'asset-files'],
];
$files = $this->getFiles($form_state);
if ($files) {
$delta = 0;
$asset_type_count = [
'css' => 0,
'js' => 0,
];
foreach ($files as $file) {
// Keep track of how many of each type of file we have.
// The first js or css file name will be the same as the Asset ID. Then
// the files will be numbered sequentially if there are multiple of the
// same type. e.g. script.js, script-1.js.
$count = &$asset_type_count[$file['type']];
$name = \sprintf('%s%s', \str_replace('_', '-', $this->entity->id()), $count > 0 ? '-' . $count : '');
$filename = \sprintf('%s.%s', $name, $file['type']);
$form['files'][$delta] = [
'name' => [
'#type' => 'value',
'#value' => $name,
],
'type' => [
'#type' => 'value',
'#value' => $file['type'],
],
'code' => [
'#type' => 'codemirror',
'#codemirror' => [
'autoCloseTags' => TRUE,
'buttons' => [
'undo',
'redo',
'enlarge',
'shrink',
],
'foldGutter' => TRUE,
'lineNumbers' => TRUE,
'lineWrapping' => TRUE,
'styleActiveLine' => TRUE,
'toolbar' => TRUE,
'lineSeparator' => "\n",
'mode' => $file['type'] == 'js' ? 'javascript' : $file['type'],
],
'#title' => $filename,
'#default_value' => $file['code'],
],
'delete' => [
'#tree' => FALSE,
'#type' => 'submit',
'#value' => $this->t('Delete @filename', ['@filename' => $filename]),
'#name' => "delete::$delta",
'#submit' => [[$this, 'deleteFile']],
'#ajax' => [
'callback' => [$this, 'fileAjax'],
'wrapper' => 'asset-files',
'effect' => 'fade',
],
'#attributes' => [
'class' => [
'button',
'button--danger',
],
],
'#delta' => $delta,
],
];
// Reset the codemirror value when a file is deleted.
[$delete] = explode('::', $form_state->getUserInput()['_triggering_element_name'] ?? '');
if ($delete === 'delete') {
$form['files'][$delta]['code']['#value'] = $file['code'];
}
$delta++;
$count++;
}
}
$form['actions']['add_css'] = [
'#type' => 'submit',
'#value' => $this->t('Add CSS'),
'#name' => 'add_css',
'#submit' => [[$this, 'addFile']],
'#ajax' => [
'callback' => [$this, 'fileAjax'],
'wrapper' => 'asset-files',
'effect' => 'fade',
],
'#asset_type' => 'css',
'#weight' => 6,
];
$form['actions']['add_js'] = [
'#type' => 'submit',
'#value' => $this->t('Add JS'),
'#name' => 'add_js',
'#submit' => [[$this, 'addFile']],
'#ajax' => [
'callback' => [$this, 'fileAjax'],
'wrapper' => 'asset-files',
'effect' => 'fade',
],
'#asset_type' => 'js',
'#weight' => 7,
];
$form['#entity_builders']['update_form_langcode'] = [$this, 'entityFormEntityBuild'];;
$form['#attached']['library'][] = 'codemirror_editor/editor';
return $form;
}
/**
* Entity builder callback.
*
* Keeps files in sync if there are no more file fields but the entity still
* lists contains some.
*/
public function entityFormEntityBuild($entity_type_id, ComponentLibraryAsset $asset, &$form, FormStateInterface &$form_state) {
if ($this->entity->getFiles() && !$form_state->getValue('files') ) {
$this->entity->setFiles([]);
}
}
/**
* Submit button handler.
*/
public function addFile(array $form, FormStateInterface $form_state) {
$files = $this->getFiles($form_state);
$files[] = [
'type' => $form_state->getTriggeringElement()['#asset_type'],
];
$this->setFiles($files, $form_state);
}
/**
* Submit button handler.
*/
public function deleteFile(array $form, FormStateInterface $form_state) {
$files = $this->getFiles($form_state);
unset($files[$form_state->getTriggeringElement()['#delta']]);
$this->setFiles($files, $form_state);
}
/**
* AJAX callback to add/remove a file.
*/
public function fileAjax(array $form, FormStateInterface $form_state): array {
return $form['files'];
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$result = parent::save($form, $form_state);
$message_args = ['%label' => $this->entity->label()];
$message = $result == \SAVED_NEW
? $this->t('Created new component_library_asset %label.', $message_args)
: $this->t('Updated component_library_asset %label.', $message_args);
$this->messenger()->addStatus($message);
return $result;
}
/**
* Get files.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*
* @return array
* An array of file values.
*/
private function getFiles(FormStateInterface $form_state) {
return $form_state->getValue('files') ?? $this->entity->getFiles();
}
/**
* Set files.
*
* @param array $files
* An array of file values.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
private function setFiles(array $files, FormStateInterface $form_state) {
if (count($files) > 1) {
\ksort($files);
}
elseif (count($files) == 1) {
// If ksort doesn't run, lets still set the delta to 0.
$key = \array_key_first($files);
$files = [$files[$key]];
}
$form_state->setValue('files', $files);
$form_state->setRebuild();
}
}
