class ComponentSectionForm in Module Builder 8.3
Generic form for entering a section of data for a component.
This determines which properties of the component to show from the values of the entity type's code_builder annotation.
Hierarchy
- class \Drupal\Core\Form\FormBase implements ContainerInjectionInterface, FormInterface uses DependencySerializationTrait, LoggerChannelTrait, MessengerTrait, LinkGeneratorTrait, RedirectDestinationTrait, UrlGeneratorTrait, StringTranslationTrait
- class \Drupal\Core\Entity\EntityForm implements EntityFormInterface
- class \Drupal\module_builder\Form\ComponentFormBase
- class \Drupal\module_builder\Form\ComponentSectionForm
- class \Drupal\module_builder\Form\ComponentFormBase
- class \Drupal\Core\Entity\EntityForm implements EntityFormInterface
Expanded class hierarchy of ComponentSectionForm
See also
\Drupal\module_builder\EntityHandler\ComponentSectionFormHandler
File
- src/
Form/ ComponentSectionForm.php, line 22
Namespace
Drupal\module_builder\FormView source
class ComponentSectionForm extends ComponentFormBase {
/**
* Gets the names of properties this form should show.
*
* @return string[]
* An array of property names.
*/
protected function getFormComponentProperties(DataItem $data) {
// Get the list of component properties this section form uses from the
// handler, which gets them from the entity type annotation.
$component_entity_type_id = $this->entity
->getEntityTypeId();
$component_sections_handler = $this->entityTypeManager
->getHandler($component_entity_type_id, 'component_sections');
$operation = $this
->getOperation();
$component_properties_to_use = $component_sections_handler
->getSectionFormComponentProperties($operation);
return $component_properties_to_use;
}
/**
* Title callback.
*
* @see \Drupal\module_builder\Routing\ComponentRouteProvider
*/
public function title(Request $request, $entity_type, $op, $title) {
// Get the entity request parameter. We can't use it as a function parameter
// because we want this to work with any entity type.
$entity = $request->attributes
->get($entity_type);
return $this
->t($title, [
'%label' => $entity
->label(),
]);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
$form = $this
->componentPropertiesForm($form, $form_state);
$form['#attached']['library'][] = 'module_builder/typed_data_defaults';
$form['#attached']['drupalSettings']['moduleBuilder']['typedDataDefaults']['defaults'] = [];
return $form;
}
/**
* Add form elements for the specified component properties.
*
* @param $form
* The form array.
* @param FormStateInterface $form_state
* The form state object.
*
* @return
* The form array.
*/
protected function componentPropertiesForm($form, FormStateInterface $form_state) {
if (!$form_state
->has('data')) {
// The first time we show this form, create the typed data object.
$component_data = $this
->getComponentDataObject();
$first_load = TRUE;
$form_state
->set('data', $component_data);
}
else {
// During an AJAX rebuild, get the data from the form state.
$component_data = $form_state
->get('data');
}
// Get the properties that this form section should show.
$component_properties_to_use = $this
->getFormComponentProperties($component_data);
if (!empty($first_load)) {
// Warn about properties in the entity annotation that are not in the
// data.
$undefined_properties = array_diff($component_properties_to_use, array_keys($component_data
->getProperties()));
foreach ($undefined_properties as $property_name) {
$this
->messenger()
->addError(t("The property '@name' is not defined in Drupal Code Builder. You should ensure you are using an up-to-date version.", [
'@name' => $property_name,
]));
}
}
$component_properties_to_use = array_intersect($component_properties_to_use, array_keys($component_data
->getProperties()));
// Set #tree on the data element.
$form['module']['#tree'] = TRUE;
foreach ($component_properties_to_use as $property_name) {
$this
->buildFormElement($form['module'], $form_state, $component_data->{$property_name});
}
// Put the data back into the form state, as the building of the form
// elements may have caused changes.
$form_state
->set('data', $component_data);
// Developer trapdoor: disable AJAX for easier debugging of the form.
if (FALSE) {
// TODO: AAAAAARGH why can't this be done with Iterator classes???
static::removeAjax($form);
}
return $form;
}
/**
* Helper to remove all ajax from the form.
*
* Use this when debugging, as if an ajax request is crashing, it's best to
* turn ajax off and use normal submission to see the error messages
* immediately rather than pick them out of the log.
*/
protected static function removeAjax(&$element) {
foreach ($element as $key => &$value) {
if (is_array($value) && isset($value['#ajax'])) {
unset($value['#ajax']);
}
if (is_array($value)) {
static::removeAjax($value);
}
}
}
/**
* Builds the form element for a data item.
*
* This is called recursively for complex and multi-valued data items.
*
* @param array &$form
* The parent form element (or the entire form), passed by reference. The
* data item's element is placed with an array key that is its machine
* name.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
* @param \MutableTypedData\Data\DataItem $data
* The data item.
*/
protected function buildFormElement(&$form, FormStateInterface $form_state, DataItem $data) {
$element = [];
// Determine whether to handle multiple data as a single element or a set
// of deltas.
// Multiple-valued data gets a set of items that can be added and removed
// with AJAX buttons...
$use_multiple_deltas = $data
->isMultiple();
// ... with exceptions: simple data with options that is multiple-
// valued is just a SELECT element.
if (!$data
->isComplex() && $data
->hasOptions()) {
$use_multiple_deltas = FALSE;
}
// ... multiple simple data without options is shown as a text area.
if (!$data
->isComplex()) {
$use_multiple_deltas = FALSE;
}
// Case 1: multiple deltas each handled as a separate form element, within
// a details wrapper.
if ($use_multiple_deltas) {
$element = $this
->buildMultipleDeltaFormElement($form, $form_state, $data);
}
elseif ($data
->getDefinition()
->isComplex()) {
$element = $this
->buildComplexFormElement($form, $form_state, $data);
}
else {
// Case 3A: element with options.
if ($data
->hasOptions()) {
$options = [];
$options_have_descriptions = FALSE;
foreach ($data
->getOptions() as $value => $option) {
$options[$value] = $option
->getLabel();
if ($description = $option
->getDescription()) {
// Set the description on each value. This is a not-terribly-well
// documented feature in FormAPI. This relies on us not clobbering
// $element further on!
$element[$value]['#description'] = $description;
$options_have_descriptions = TRUE;
}
}
$options_count = count($options);
if ($data
->isMultiple()) {
$element_type = 'checkboxes';
// Build up a default value array for the checkboxes from the data's
// delta items.
$default_value = [];
foreach ($data as $delta => $delta_item) {
$default_value[$delta_item->value] = $delta_item->value;
}
}
else {
if ($options_count > 8 && !$options_have_descriptions) {
$element_type = 'select';
$default_value = $data->value;
}
else {
$element_type = 'radios';
$default_value = $data->value;
}
}
natcasesort($options);
$element += [
'#type' => $element_type,
'#title' => $data
->getLabel(),
'#description' => $data
->getDescription(),
'#default_value' => $default_value,
'#options' => $options,
// ARGH why isn't this happening automatically like it's supposed to?
'#empty_option' => $data
->isRequired() ? $this
->t('- Select -') : $this
->t('- None -'),
'#empty_value' => NULL,
];
// Special handling for injected services: textfield with autocomplete.
if (in_array($data
->getName(), [
'injected_services',
'container_services',
'mocked_services',
])) {
$element['#type'] = 'textfield';
// This needs to be massive to allow lots of services!
$element['#maxlength'] = 512;
$element['#description'] .= ' ' . $this
->t("Enter a comma-separated list of names.");
$element['#default_value'] = implode(', ', $default_value);
$element['#autocomplete_route_name'] = 'module_builder.autocomplete';
$element['#autocomplete_route_parameters'] = [
'property_address' => $data
->getAddress(),
];
// Remove the options, as it makes FormAPI think the value must be
// compared against them.
unset($element['#options']);
}
if ($data
->isVariantProperty()) {
// Put this above the 'Update variant properties' button; compare
// with the weight set on that.
$element['#weight'] = -20;
$wrapper_id = Html::getId($data
->getParent()
->getAddress() . '-mutable-wrapper');
$variant_property_form_address = explode(':', $data
->getAddress());
$variant_property_address = array_merge($variant_property_form_address);
$values = $form_state
->getValues();
$variant_value = NestedArray::getValue($values, $variant_property_address);
if (isset($variant_value)) {
$data
->set($variant_value);
}
}
}
elseif ($data
->getType() == 'boolean') {
$element += [
'#type' => 'checkbox',
'#title' => $data
->getLabel(),
'#description' => $data
->getDescription(),
'#default_value' => $data->value,
];
}
elseif ($data
->isMultiple()) {
$element += [
'#type' => 'textarea',
'#title' => $data
->getLabel(),
'#description' => $data
->getDescription(),
'#default_value' => implode("\n", $data
->export()),
];
}
else {
$element += [
'#type' => 'textfield',
'#title' => $data
->getLabel(),
'#description' => $data
->getDescription(),
'#default_value' => $data->value,
];
}
// Make form elements required if the data is required, unless there is
// a default, in which case, either the JS will set it, or data validation
// will set it on submission, so there's no need to force the user to
// enter something.
if ($data
->isRequired() && !$data
->getDefault()) {
$element['#required'] = TRUE;
}
$element['#attributes']['data-typed-data-address'] = $data
->getAddress();
// dsm($data->getDefault());
// Note need parentheses around the assignment because of precedence
// relative to &&.
if (($default = $data
->getDefault()) && $default
->getType() == 'expression') {
$expression = $default
->getExpressionWithAbsoluteAddresses($data);
// Prefix custom EL functions with the JS namespace.
// TODO: Would be nice to get the names from the EL rather than
// hardcode them!
$expression = str_replace('get(', 'DataAddressExpressionLanguage.get(', $expression);
$expression = str_replace('machineToClass(', 'DataAddressExpressionLanguage.machineToClass(', $expression);
$expression = str_replace('machineToLabel(', 'DataAddressExpressionLanguage.machineToLabel(', $expression);
$expression = str_replace('stripBefore(', 'DataAddressExpressionLanguage.stripBefore(', $expression);
$dependencies = $default
->getDependencies();
if (!empty($dependencies)) {
// CHEAT; for now only ever one dependency!
// TODO: this only works for addresses that go up only one level!
$dependencies[0] = str_replace('..:', $data
->getParent()
->getAddress() . ':', $dependencies[0]);
}
$element['#attached']['drupalSettings']['moduleBuilder']['typedDataDefaults']['defaults'][$this
->getFormElementNameFromData($data)] = [
'dependencies' => $dependencies,
'expression' => $expression,
];
foreach ($dependencies as $dependency) {
$element['#attached']['drupalSettings']['moduleBuilder']['typedDataDefaults']['reactions'][$dependency] = $this
->getFormElementNameFromData($data);
}
}
}
$element['#tree'] = TRUE;
$form_key = $data
->getName();
$form[$form_key] = $element;
}
protected function getFormElementNameFromData($data_item) {
$pieces = explode(':', $data_item
->getAddress());
$name = array_shift($pieces);
foreach ($pieces as $piece) {
$name .= "[{$piece}]";
}
return $name;
}
/**
* Builds a multi-valued form element.
*
* Helper for buildFormElement().
*
* Note that buildFormElement() is responsible for some attributes of the
* element.
*/
protected function buildMultipleDeltaFormElement(&$form, FormStateInterface $form_state, DataItem $data) {
// Set up a wrapper for AJAX.
$wrapper_id = Html::getId($data
->getAddress() . '-add-more-wrapper');
// Use 'details' rather than 'container' so there's a visual indicator
// of the multi-valued property.
$element = [
'#type' => 'details',
'#title' => $data
->getLabel(),
'#open' => TRUE,
'#attributes' => [
'id' => $wrapper_id,
],
];
foreach ($data as $delta => $delta_item) {
$this
->buildFormElement($element, $form_state, $delta_item);
// Set the label on each delta item to differentiate it from the overall
// element label.
$element[$delta]['#title'] = $delta_item
->getLabel();
// Doesn't work; see removeItemSubmit().
// $element[':' . $delta_item->getName() . '_remove_button'] = [
// '#type' => 'submit',
// // Needs to be full address for uniquess in the whole form.
// '#name' => $data->getAddress() . '_remove_item',
// '#value' => t('Remove item'),
// // Hack?
// '#input' => $delta,
// '#limit_validation_errors' => [],
// '#submit' => ['::removeItemSubmit'],
// '#ajax' => [
// 'callback' => '::itemButtonAjax',
// 'wrapper' => $wrapper_id,
// 'effect' => 'fade',
// ],
// ];
}
if ($data
->mayAddItem()) {
if (count($data)) {
$button_label = $this
->t('Add another @label item', [
'@label' => $data
->getLabel(),
]);
}
else {
$button_label = $this
->t('Add a @label item', [
'@label' => $data
->getLabel(),
]);
}
$element[':add_button'] = [
'#type' => 'submit',
// This allows FormAPI to figure out which button is the triggering
// element. The name must be unique across all buttons in the form,
// otherwise, the first matching name will be taken by FormAPI as being
// the button that was clicked, with unexpected results.
// See \Drupal\Core\Form\FormBuilder::elementTriggeredScriptedSubmission().
'#name' => $data
->getAddress() . '_add_more',
'#value' => $button_label,
'#limit_validation_errors' => [],
'#submit' => [
'::addItemSubmit',
],
'#data_address' => $data
->getAddress(),
'#ajax' => [
'callback' => '::itemButtonAjax',
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
'#prefix' => '<div>',
'#suffix' => '</div>',
];
}
if (count($data)) {
$element[':remove_button'] = [
'#type' => 'submit',
// Needs to be full address for uniquess in the whole form.
'#name' => $data
->getAddress() . '_remove_item',
'#value' => $this
->t('Remove last item'),
'#limit_validation_errors' => [],
'#submit' => [
'::removeItemSubmit',
],
'#data_address' => $data
->getAddress(),
'#ajax' => [
'callback' => '::itemButtonAjax',
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
'#prefix' => '<div>',
'#suffix' => '</div>',
];
}
return $element;
}
/**
* Builds a form element with multiple child elements.
*
* Helper for buildFormElement().
*
* Note that buildFormElement() is responsible for some attributes of the
* element.
*/
protected function buildComplexFormElement(&$form, FormStateInterface $form_state, DataItem $data) {
// Set up a wrapper for AJAX.
$wrapper_id = Html::getId($data
->getAddress() . '-complex-wrapper');
$element = [
'#type' => 'details',
'#title' => $data
->getLabel(),
'#open' => TRUE,
'#attributes' => [
'id' => $wrapper_id,
],
];
// Don't show an optional and single-valued complex element until the user
// requests it. This is to keep the form clear, and to prevent validation
// errors of the complex element has required properties but the user
// doesn't want it.
if (!$data
->isRequired() && $data
->isEmpty() && !$data
->isDelta()) {
$element[':add_button'] = [
'#type' => 'submit',
// This allows FormAPI to figure out which button is the triggering
// element. The name must be unique across all buttons in the form,
// otherwise, the first matching name will be taken by FormAPI as being
// the button that was clicked, with unexpected results.
// See \Drupal\Core\Form\FormBuilder::elementTriggeredScriptedSubmission().
'#name' => $data
->getAddress() . '_add',
'#value' => $this
->t('Add @component', [
'@component' => $data
->getLabel(),
]),
'#limit_validation_errors' => [],
'#submit' => [
'::addComplexDataSubmit',
],
'#data_address' => $data
->getAddress(),
'#ajax' => [
'callback' => '::complexButtonAjax',
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
'#prefix' => '<div>',
'#suffix' => '</div>',
];
return $element;
}
foreach ($data as $data_item) {
$this
->buildFormElement($element, $form_state, $data_item);
}
if (!$data
->isRequired() && !$data
->isEmpty() && !$data
->isDelta()) {
$element[':remove_button'] = [
'#type' => 'submit',
// This allows FormAPI to figure out which button is the triggering
// element. The name must be unique across all buttons in the form,
// otherwise, the first matching name will be taken by FormAPI as being
// the button that was clicked, with unexpected results.
// See \Drupal\Core\Form\FormBuilder::elementTriggeredScriptedSubmission().
'#name' => $data
->getAddress() . '_remove',
'#value' => $this
->t('Remove @component', [
'@component' => $data
->getLabel(),
]),
'#limit_validation_errors' => [],
'#submit' => [
'::removeComplexDataSubmit',
],
'#data_address' => $data
->getAddress(),
'#ajax' => [
'callback' => '::complexButtonAjax',
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
'#prefix' => '<div>',
'#suffix' => '</div>',
];
}
// NO! has to go immediately after the variant property!
if ($data
->isMutable()) {
if (count($data
->getProperties()) == 1 && $data
->getVariantData()->value) {
$element['count_notice'] = [
'#type' => 'container',
'notice' => [
'#plain_text' => $this
->t("The @variant variant has no additional properties.", [
'@variant' => $data
->getVariantData()
->getLabel(),
]),
],
];
}
// Set up a wrapper for AJAX.
// Note that we don't to use Html::getUniqueId() because the data's
// address is already unique, and furthermore, we don't WANT uniqueness
// because we want the same data item to produce the same HTML ID when
// we're looking at the variant property form element further on.
// TODO: this assumes only one root data item in the form, as another
// data item could have the same addresses!
$wrapper_id = Html::getId($data
->getAddress() . '-mutable-wrapper');
$element['#attributes'] = [
'id' => $wrapper_id,
];
// WARNING: assumes the form structure!
$mutable_property_form_address = explode(':', $data
->getAddress());
$variant_property_address = array_merge($mutable_property_form_address, [
$data
->getVariantData()
->getName(),
]);
$element[':update_variant'] = [
'#type' => 'submit',
// Needs to be full address for uniquess in the whole form.
'#name' => $data
->getAddress() . '_update_variant',
// TODO: customisable!
'#value' => $data
->isEmpty() ? $this
->t('Set variant') : $this
->t('Change variant and delete data for this item'),
// We need to validate the variant property so we get its value in the
// submit handler for this button.
'#limit_validation_errors' => [
$variant_property_address,
],
'#element_validate' => [
'::updateVariantValidate',
],
'#submit' => [
'::updateVariantSubmit',
],
'#data_address' => $data
->getVariantData()
->getAddress(),
'#variant_data_name' => $data
->getVariantData()
->getName(),
'#ajax' => [
'callback' => '::variantButtonAjax',
'wrapper' => $wrapper_id,
'effect' => 'fade',
],
'#prefix' => '<div>',
'#suffix' => '</div>',
'#weight' => -10,
];
// Also tweak the weight of the variant property so it goes above the
// button to change the variant.
$variant_property_name = $data
->getVariantData()
->getName();
$element[$variant_property_name]['#weight'] = -20;
}
return $element;
}
/**
* Submission handler for the "Add another item" buttons.
*/
public static function addItemSubmit(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
// Get the data item, using the address set in the button.
$data = $form_state
->get('data');
$data_item = $data
->getItem($button['#data_address']);
// Add a new delta item.
$data_item
->createItem();
$form_state
->set('data', $data);
$form_state
->setRebuild();
}
/**
* Submission handler for the "Remove item" buttons.
*/
public static function removeItemSubmit(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
// Get the data item, using the address set in the button.
$data = $form_state
->get('data');
$multiple_data_item = $data
->getItem($button['#data_address']);
// dsm($multiple_data_item);
// We could remove any of the items here, but the problem is then that
// FormAPI appears to put the currently entered values back into the form
// elements whose deltas have closed the gap, which makes it look like it
// was the last one that was removed anyway.
$last_delta = count($multiple_data_item) - 1;
unset($multiple_data_item[$last_delta]);
$form_state
->set('data', $data);
$form_state
->setRebuild();
}
/**
* Ajax callback for the item count buttons.
*
* This returns the new page content to replace the page content made obsolete
* by the form submission.
*/
public static function itemButtonAjax(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
// Go up in the form, to the widgets container.
$button_array_parents = $button['#array_parents'];
$widgets_container_parents = array_slice($button_array_parents, 0, -1);
$element = NestedArray::getValue($form, $widgets_container_parents);
return $element;
}
public static function addComplexDataSubmit(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
$data = $form_state
->get('data');
$complex_data_item = $data
->getItem($button['#data_address']);
// Access the data to cause it to instantiate.
$complex_data_item
->access();
$form_state
->set('data', $data);
$form_state
->setRebuild();
}
public static function removeComplexDataSubmit(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
$data = $form_state
->get('data');
$complex_data_item = $data
->getItem($button['#data_address']);
$complex_data_item
->unset();
$form_state
->set('data', $data);
$form_state
->setRebuild();
}
public static function complexButtonAjax(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
// Go up in the form, to the widgets container.
$button_array_parents = $button['#array_parents'];
$widgets_container_parents = array_slice($button_array_parents, 0, -1);
$element = NestedArray::getValue($form, $widgets_container_parents);
return $element;
}
/**
* Validate handler for the update variant button.
*
* This removes variant-dependent values if the variant has changed.
*/
public static function updateVariantValidate(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
// WTF FormAPI?
// Why does this
if (array_pop($button['#parents']) != ':update_variant') {
return;
}
// Get the value of the mutable data variant property.
$values_address = $button['#parents'];
$values_address[] = $button['#variant_data_name'];
$values = $form_state
->getValues();
$submitted_variant_value = NestedArray::getValue($values, $values_address);
// Checking the variant property form element is #required hasn't happened
// yet at this point. So bail and let FormAPI set a form error.
if (empty($submitted_variant_value)) {
return;
}
// Get the containing variant data item, using the address set in the
// button.
$data = $form_state
->get('data');
$variant_data_item = $data
->getItem($button['#data_address']);
// Clean up the values if the current submission is changing the variant
// property.
// We can't determine whether this is happening by comparing the form
// state's variant value with the data item's variant value, because
// validateForm()'s call to CleanUpValues() has already set the values on
// the data item, and then set that back in the form state. Which is ugly,
// because it means a button validator such as this one doesn't have a
// proper picture of what is going on. TODO: look at core entity forms and
// see whether they set the entity back on the form after building it in
// validation. A quick look suggests that they don't.
$variant_properties = $variant_data_item
->getParent()
->getProperties();
$complex_values_address = $button['#parents'];
$complex_values = NestedArray::getValue($values, $complex_values_address);
$cleaned_complex_values = array_intersect_key($complex_values, $variant_properties);
NestedArray::setValue($values, $complex_values_address, $cleaned_complex_values);
$form_state
->setValues($values);
// TODO: this bit WOULD work if validateForm() weren't updating the data
// item stored in the form state. As it stands, it does nothing because
// the two values will be equal even when the variant is being changed.
if ($submitted_variant_value != $variant_data_item->value) {
$complex_values_address = $button['#parents'];
$complex_values = NestedArray::getValue($values, $complex_values_address);
$complex_values = array_intersect_key($complex_values, [
$button['#variant_data_name'] => TRUE,
]);
NestedArray::setValue($values, $complex_values_address, $complex_values);
$form_state
->setValues($values);
}
}
/**
* Submission handler for the "Update variants" buttons.
*/
public static function updateVariantSubmit(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
// dsm($button);
// Get the value of the mutable data variant property.
$values_address = array_slice($button['#parents'], 0, -1);
$values_address[] = $button['#variant_data_name'];
// Get the containing variant data item, using the address set in the
// button.
$data = $form_state
->get('data');
$variant_data_item = $data
->getItem($button['#data_address']);
$values = $form_state
->getValues();
$variant_value = NestedArray::getValue($values, $values_address);
$variant_data_item->value = $variant_value;
$form_state
->set('data', $data);
// dsm($data);
$form_state
->setRebuild();
}
/**
* Ajax callback for the variant buttons.
*
* This returns the new page content to replace the page content made obsolete
* by the form submission.
*/
public static function variantButtonAjax(array $form, FormStateInterface $form_state) {
$button = $form_state
->getTriggeringElement();
// Go up in the form, to the widgets container.
$button_array_parents = $button['#array_parents'];
// Get the address of the containing multiple data item.
// WARNING: this assumes the 'data' form element is at the top in the
// form structure!
$widgets_container_parents = array_slice($button_array_parents, 0, -1);
$element = NestedArray::getValue($form, $widgets_container_parents);
return $element;
}
/**
* Ajax callback for the variant elements.
*
* This returns the new page content to replace the page content made obsolete
* by the form submission.
*/
public static function variantElementAjax(array $form, FormStateInterface $form_state) {
$variant_element = $form_state
->getTriggeringElement();
// Go up in the form, to the widgets container.
$variant_element_array_parents = $variant_element['#array_parents'];
// Get the address of the containing multiple data item.
// WARNING: this assumes the 'data' form element is at the top in the
// form structure!
if ($variant_element['#type'] == 'radios') {
$widgets_container_parents = array_slice($variant_element_array_parents, 0, -2);
}
elseif ($variant_element['#type'] == 'select') {
$widgets_container_parents = array_slice($variant_element_array_parents, 0, -1);
}
$element = NestedArray::getValue($form, $widgets_container_parents);
return $element;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
$data = $form_state
->get('data');
// EntityForm::submitForm() has already called $form_state->cleanValues().
$data_values = $form_state
->getValue($data
->getName());
// ARGH. Because FormValidator does general validation before element or
// button validation, we call this BEFORE button-specific validation can do
// things to clean up things specific to the button's action, such as
// removing data for a changed variant.
$this
->cleanUpValues($data_values, $data);
// Clear the properties we use in this form, so we don't merge with what's
// already there. Note that $data is initially loaded from the component
// entity when the form is built.
$component_properties_to_use = $this
->getFormComponentProperties($data);
foreach ($component_properties_to_use as $property_name) {
// dsm("CLEAR $property_name");
$data
->removeItem($property_name);
}
try {
$data
->set($data_values);
} catch (InvalidInputException $e) {
$form_state
->setError($form, $this
->t("There was a problem with the form data."));
}
// Validate the data and set any violations as form errors.
// TODO: we've validating the whole data, some of which doesn't appear on
// the form -- but there shouldn't be violations outside of the form, since
// those would have been caught when their form page was saved! In theory!
$violations = $data
->validate();
foreach ($violations as $address => $violation_messages) {
$form_address = explode(':', $address);
$key_exists = NULL;
$form_element = NestedArray::getValue($form, $form_address, $key_exists);
// Some form elements group all the deltas of a data item together, such
// as injected services and textareas. In that case, there is no element
// for the actual delta, and the error should be set on the parent
// address.
if (!$key_exists) {
array_pop($form_address);
// dsm($form_address);
$form_element = NestedArray::getValue($form, $form_address, $key_exists);
}
// Filter the violations to those elements shown on this form section.
// The 2nd element of the address corresponds to the names in the
// $component_properties_to_use array (since the 1st element is 'module).
if (!in_array($form_address[1], $component_properties_to_use)) {
continue;
}
foreach ($violation_messages as $violation_message) {
$form_state
->setError($form_element, $violation_message);
}
}
// TODO: not sure we should do this here! it means that element and button
// validators have an incorrect picture of what is going on!
// TODO: figure out why values filled in by validation don't make it to the
// form at this point!
$form_state
->set('data', $data);
}
/**
* Copies top-level form values to entity properties
*
* This should not change existing entity properties that are not being edited
* by this form.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity the current form should operate upon.
* @param array $form
* A nested array of form elements comprising the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
$data = $form_state
->get('data');
// EntityForm::submitForm() has already called $form_state->cleanValues().
$data_values = $form_state
->getValue($data
->getName());
$this
->cleanUpValues($data_values, $data);
// We need to preserve any data for properties not on this form, but
// completely clear properties that are on this form so we don't merge into
// any existing data.
// Note that $data is initially loaded from the component entity when the
// form is built.
$component_properties_to_use = $this
->getFormComponentProperties($data);
foreach ($component_properties_to_use as $property_name) {
$data
->removeItem($property_name);
}
try {
$data
->set($data_values);
} catch (InvalidInputException $e) {
watchdog_exception('module_builder', $e);
$this
->messenger()
->addError($this
->t("There was a problem with the form data. The component was not saved."));
return;
}
// Set the name and ID, which use form elements outside of MTD.
// This only applies to the 'name' component section form.
if ($form_state
->getValue('id')) {
$data->root_name = $form_state
->getValue('id');
$data->readable_name = $form_state
->getValue('name');
}
// Let validation fill in required values with defaults, which we didn't
// mark as required in the form.
$data
->validate();
$data_export = $data
->export();
$entity
->set('data', $data_export);
}
/**
* Recursively clean up the submitted form values.
*
* Note that this is called by validateForm(), and so is run BEFORE any
* button-specific validators. This means that it can't rely on any action-
* specific clean up of values.
*
* @param array &$array
* An array of form values, passed by reference. This will be altered in
* place.
* @param \MutableTypedData\Data\DataItem $data
* The data item that corresponds to the form values array.
*/
protected function cleanUpValues(&$array, DataItem $data) {
// Clean up the data for the old variant if mutable data is having its
// variant changed.
if ($data
->isMutable() && !$data
->isMultiple()) {
$variant_property_name = $data
->getVariantData()
->getName();
if ($array[$variant_property_name] != $data
->getVariantData()->value) {
// The variant property has been changed. Remove everything from the
// array except for the variant property.
$array = [
$variant_property_name => $array[$variant_property_name],
];
}
}
foreach ($array as $key => &$value) {
// Remove buttons.
if (substr($key, 0, 1) == ':') {
unset($array[$key]);
continue;
}
// Single checkbox.
// TODO ARRRGH can't figure out how to safely get a child item in all
// circumstances.
// See https://github.com/joachim-n/mutable-typed-data/issues/3
if (!is_numeric($key) && $data->{$key}
->getType() == 'boolean') {
$array[$key] = (bool) $array[$key];
continue;
}
// Remove empty values, so for example, empty checkboxes in a set don't
// try to set an empty value on the data item.
if (is_null($value)) {
unset($array[$key]);
continue;
}
// Remove empty mutable data. This means that we remove the empty option
// during an AJAX call to add a delta to a multi-valued mutable item,
// which can't be set on the data as a variant can't be set to an empty
// value.
// TODO: needs test coverage.
if ($data
->isMutable()) {
if (empty($value)) {
unset($array[$key]);
continue;
}
}
if (is_array($value)) {
if (is_numeric($key)) {
$this
->cleanUpValues($value, $data[$key]);
}
elseif ($data->{$key}
->hasOptions()) {
// We're dealing with checkboxes. Convert the keyed array to an numeric
// array.
$value = array_values(array_filter($value));
}
else {
$this
->cleanUpValues($value, $data->{$key});
}
// If the value array is now empty (because recursively cleaning it has
// removed all its keys), remove it.
if (empty($value)) {
unset($array[$key]);
}
// For FKW reasons, the form state values get order mixed up when
// the variant type is set on an additional variant. The new value is
// first in the array, which MTD won't accept because it expects deltas
// to be in the correct order. Values are in the right order in
// updateVariantSubmit(), but in the wrong order when
// when copyFormValuesToEntity() is called.
// I've given up figuring out why FormAPI does this so here is a hack
// to fix the values.
// See https://www.drupal.org/project/module_builder/issues/3173604
if (!empty($value) && is_numeric(array_keys($value)[0])) {
ksort($value);
}
}
else {
// Some single values need special handling too.
// Handle a textarea.
if ($data->{$key}
->isMultiple() && !$data->{$key}
->isComplex() && !$data->{$key}
->hasOptions()) {
// Text area line breaks are weird, apparently.
$values = preg_split("@[\r\n]+@", $value);
$values = array_filter($values);
$array[$key] = $values;
}
if (in_array($data->{$key}
->getName(), [
'injected_services',
'container_services',
'mocked_services',
])) {
// Form elements for injected services need special handling.
$value = preg_split("@[,]+@", $value);
$value = array_filter($value);
$array[$key] = array_map('trim', $value);
}
elseif (!is_numeric($key) && $data->{$key}
->hasOptions() && !$data->{$key}
->isRequired()) {
// Options elements that are not required should be cleaned up, so we
// don't set an empty string as the data value.
unset($array[$key]);
}
}
}
}
/**
* Builds the form element for a component.
*
* This builds the root level form element, or an element for any part of
* the property info array that is an array of properties. This is recursed
* into by elementCompound().
*
* @param array $property_address
* The property address for the component. This is an array that gives the
* location of this component's properties list in the complete property info array
* in static::$componentDataInfo. For the root, this will be an empty array;
* for a child compound property this will be an address of the form
* parent->properties->child->properties.
* @param array $value_address
* The value address for the form element to be created. This is similar to
* the property address, but will include items for compound property deltas.
* This ensures that buttons and item counts in form storage are unique for
* compound elements which are themselves children of multi-valued compound
* elements.
* @param $form_value_address
* The form values address for the component. This is used to set the
* #parents property on the form element we create, so that the form values
* structure matches the original data structure. This is different again
* from the other two addresses, as it does not include a level for the
* 'properties' array, but does include deltas.
*
* @return array
* The form array for the component's element.
*/
// TODO: mine this and helpers for old code.
private function getCompomentElement($form_state, $property_address, $value_address, $form_value_address) {
$component_element = [];
$properties = NestedArray::getValue(static::$componentDataInfo, $property_address);
// TODO: should this be carried through? Check whether preparing a compound
// property can set values in the array.
$component_data = [];
foreach ($properties as $property_name => &$property_info) {
// Prepare the single property: get options, default value, etc.
$this->codeBuilderTaskHandlerGenerate
->prepareComponentDataProperty($property_name, $property_info, $component_data);
// Skip the properties that we're not showing on this form section.
if (!empty($property_info['hidden'])) {
continue;
}
// Add the name of the current property to the address arrays.
$property_component_address = $property_address;
$property_component_address[] = $property_name;
$property_value_address = $value_address;
$property_value_address[] = $property_name;
$property_form_value_address = $form_value_address;
$property_form_value_address[] = $property_name;
// Create a basic form element for the property.
$property_element = [
'#title' => $property_info['label'],
'#required' => $property_info['required'],
'#mb_property_address' => $property_component_address,
'#mb_value_address' => $property_value_address,
// Explicitly set this so we control the structure of the form
// submission values. In particular, we don't want to have to pick data
// out from the the structure the 'table' element would create.
'#parents' => $property_form_value_address,
];
if (isset($property_info['description'])) {
$property_element['#description'] = $property_info['description'];
}
// Add description to properties that can get defaults filled in by
// DCB in processing.
if (!empty($property_info['process_default'])) {
$property_element['#required'] = FALSE;
$property_element['#description'] = (isset($property_element['#description']) ? $property_element['#description'] . ' ' : '') . t("Leave blank for a default value.");
}
// Determine the default value to present in the form element.
// (Compound elements don't have a default value as they are just
// containers, but we use the count of the array we get to determine how
// many deltas to show.)
$key_exists = NULL;
$form_default_value = NestedArray::getValue($this->moduleEntityData, array_slice($property_form_value_address, 1), $key_exists);
// If there is no value set in the module entity data, take the default
// value that prepareComponentDataProperty() set.
if (!$key_exists) {
$form_default_value = $component_data[$property_name];
if ($property_info['format'] == 'compound') {
// Bit of a hack: for compound properties, zap the prepared default.
// The problem is that this will cause a child element to appear in
// the form, rather than starting with a zero delta.
// This happens for example with the PHPUnit test component, where
// the prepared default for the test_modules property tries to set a
// module name derived from the test class name.
// This will be fixed in DCB 3.3.x when we get the ability to do
// defaults in JS.
$form_default_value = [];
}
}
// The type of the form element depends on the format of the component data
// property.
$format = $property_info['format'];
$format_method = 'element' . ucfirst($format);
if (!method_exists($this, $format_method)) {
throw new \Exception("No method '{$format_method}' exists to handle property '{$property_name}' with format '{$format}'.");
continue;
}
$handling = $this
->{$format_method}($property_element, $form_state, $property_info, $form_default_value);
$property_form_value_address_key = implode(':', $property_form_value_address);
$form_state
->set([
'element_handling',
$property_form_value_address_key,
], $handling);
$component_element[$property_name] = $property_element;
}
return $component_element;
}
/**
* Set form element properties specific to array component properties.
*
* @param &$element
* The form element for the component property.
* @param FormStateInterface $form_state
* The form state.
* @param $property_info
* The info array for the component property.
* @param $form_default_value
* The default value for the form element.
*
* @return string
* The handling type to be applied to this element's value on submit.
*/
protected function XelementArray(&$element, FormStateInterface $form_state, $property_info, $form_default_value) {
if (isset($property_info['options'])) {
if (isset($property_info['options_extra'])) {
// Show an autocomplete textfield.
// TODO: use Select or Other module for this when it has a stable
// release.
$element['#type'] = 'textfield';
$element['#maxlength'] = 512;
$element['#description'] = (isset($element['#description']) ? $element['#description'] . ' ' : '') . t("Enter multiple values separated with a comma.");
$element['#autocomplete_route_name'] = 'module_builder.autocomplete';
$element['#autocomplete_route_parameters'] = [
'property_address' => implode(':', $element['#mb_property_address']),
];
if ($form_default_value) {
$form_default_value = implode(', ', $form_default_value);
}
$handling = 'autocomplete';
}
else {
$element['#type'] = 'checkboxes';
$element['#options'] = $property_info['options'];
if (is_null($form_default_value)) {
$form_default_value = [];
}
else {
$form_default_value = array_combine($form_default_value, $form_default_value);
}
$handling = 'checkboxes';
}
}
else {
$element['#type'] = 'textarea';
if (isset($element['#description'])) {
$element['#description'] .= ' ';
}
else {
$element['#description'] = '';
}
$element['#description'] .= t("Enter one item per line.");
// Handle a property that DCB has added since the component was saved.
if (empty($form_default_value) && !is_array($form_default_value)) {
$form_default_value = [];
}
$form_default_value = implode("\n", $form_default_value);
$handling = 'textarea';
}
$element['#default_value'] = $form_default_value;
return $handling;
}
/**
* Set form element properties specific to boolean component properties.
*
* @param &$element
* The form element for the component property.
* @param FormStateInterface $form_state
* The form state.
* @param $property_info
* The info array for the component property.
* @param $form_default_value
* The default value for the form element.
*
* @return string
* The handling type to be applied to this element's value on submit.
*/
protected function XelementBoolean(&$element, FormStateInterface $form_state, $property_info, $form_default_value) {
$element['#type'] = 'checkbox';
$element['#default_value'] = $form_default_value;
return 'checkbox';
}
/**
* Set form element properties specific to compound component properties.
*
* @param &$element
* The form element for the component property.
* @param FormStateInterface $form_state
* The form state.
* @param $property_info
* The info array for the component property.
* @param $form_default_value
* The default value for the form element.
*
* @return string
* The handling type to be applied to this element's value on submit.
*/
protected function XelementCompound(&$element, FormStateInterface $form_state, $property_info, $form_default_value) {
// A compound property shows a details element, for which we recurse and
// show another component.
$element['#type'] = 'details';
$element['#open'] = TRUE;
// Figure out how many items to show.
// If we're reloading the form in response to the 'add more' button, then
// form storage dictates the item count.
// If there's nothing set in form storage yet, it's the first time we're
// here and the number of items in the entity tells us how many items to
// show in the form.
// Finally, if that's empty, then show no items, just a button to add one.
$item_count = static::getCompoundPropertyItemCount($form_state, $element['#mb_value_address']);
if (is_null($item_count)) {
$item_count = count($form_default_value);
static::setCompoundPropertyItemCount($form_state, $element['#mb_value_address'], $item_count);
}
if (empty($item_count)) {
$item_count = 0;
static::setCompoundPropertyItemCount($form_state, $element['#mb_value_address'], $item_count);
}
// Property cardinality overrides anything else.
if (isset($property_info['cardinality'])) {
$item_count = min($item_count, $property_info['cardinality']);
if ($item_count == $property_info['cardinality']) {
// We're at the maximum item count.
$add_more = FALSE;
}
else {
// We're not yet at the cardinality: we can add more.
$add_more = TRUE;
}
}
else {
// Unlimited cardinality: can always add more.
$add_more = TRUE;
}
// Set up a wrapper for AJAX.
$wrapper_id = Html::getUniqueId(implode('-', $element['#mb_value_address']) . '-add-more-wrapper');
// TODO - use '#type' => 'container',?
$element['#prefix'] = '<div id="' . $wrapper_id . '">';
$element['#suffix'] = '</div>';
// Show the items in a table. This is single-column, with all child
// properties in the one cell, but we just want the striping for visual
// clarity.
$element['table'] = array(
'#type' => 'table',
);
// The address in the properties array to find this component's properties
// list.
$component_properties_address = $element['#mb_property_address'];
$component_properties_address[] = 'properties';
$component_value_address = $element['#mb_value_address'];
$component_value_address[] = 'properties';
$property_form_value_address = $element['#parents'];
for ($delta = 0; $delta < $item_count; $delta++) {
$row = [];
$delta_value_address = $component_value_address;
$delta_value_address[] = $delta;
$delta_form_value_address = $property_form_value_address;
$delta_form_value_address[] = $delta;
// Put all the properties into a single cell so it's a 1-column table.
// TODO: WTF NO STRIPING IN SEVEN THEME???
$delta_component_element = $this
->getCompomentElement($form_state, $component_properties_address, $delta_value_address, $delta_form_value_address, []);
$row['row'] = $delta_component_element;
$element['table'][$delta] = $row;
}
if ($add_more) {
// Show a button to add items, if they can be added.
$button_text = $item_count == 0 ? t('Add a @label item', [
'@label' => strtolower($property_info['label']),
]) : t('Add another @label item', [
'@label' => strtolower($property_info['label']),
]);
$element['actions']['add'] = array(
'#type' => 'submit',
// This allows FormAPI to figure out which button is the triggering
// element. The name must be unique across all buttons in the form,
// otherwise, the first matching name will be taken by FormAPI as being
// the button that was clicked, with unexpected results.
// See \Drupal\Core\Form\FormBuilder::elementTriggeredScriptedSubmission().
'#name' => implode(':', $element['#mb_value_address']) . '_add_more',
'#value' => $button_text,
'#limit_validation_errors' => [],
'#submit' => array(
array(
get_class($this),
'addItemSubmit',
),
),
'#ajax' => array(
'callback' => array(
get_class($this),
'itemButtonAjax',
),
'wrapper' => $wrapper_id,
'effect' => 'fade',
),
);
}
if ($item_count > 0) {
$element['actions']['remove'] = [
'#type' => 'submit',
'#name' => implode(':', $element['#mb_value_address']) . '_remove_item',
'#value' => t('Remove last item'),
'#limit_validation_errors' => [],
'#submit' => array(
array(
get_class($this),
'removeItemSubmit',
),
),
'#ajax' => array(
'callback' => array(
get_class($this),
'itemButtonAjax',
),
'wrapper' => $wrapper_id,
'effect' => 'fade',
),
];
}
return 'compound';
}
/**
* Set form element properties specific to array component properties.
*
* @param &$element
* The form element for the component property.
* @param FormStateInterface $form_state
* The form state.
* @param $property_info
* The info array for the component property.
* @param $form_default_value
* The default value for the form element.
*
* @return string
* The handling type to be applied to this element's value on submit.
*/
protected function XelementString(&$element, FormStateInterface $form_state, $property_info, $form_default_value) {
if (isset($property_info['options'])) {
$element['#type'] = 'select';
$options = [];
$element['#options'] = $property_info['options'];
$element['#empty_value'] = '';
if (empty($form_default_value)) {
$form_default_value = '';
}
$handling = 'select';
}
else {
$element['#type'] = 'textfield';
$handling = 'textfield';
}
$element['#default_value'] = $form_default_value;
return $handling;
}
/**
* Returns an array of supported actions for the current entity form.
*/
protected function actions(array $form, FormStateInterface $form_state) {
// TODO: remove #mb_action, use #name instead.
$actions['submit'] = array(
'#type' => 'submit',
'#value' => $this
->t('Save'),
'#dropbutton' => 'mb',
// Still no way to get a button's name, apparently?
'#mb_action' => 'submit',
'#submit' => array(
'::submitForm',
'::save',
),
);
if ($this
->getNextLink() != 'generate-form') {
$actions['submit_next'] = array(
'#type' => 'submit',
'#value' => $this
->t('Save and go to next page'),
'#dropbutton' => 'mb',
'#mb_action' => 'submit_next',
'#submit' => array(
'::submitForm',
'::save',
),
);
}
$actions['submit_generate'] = array(
'#type' => 'submit',
'#value' => $this
->t('Save and generate code'),
'#dropbutton' => 'mb',
'#mb_action' => 'submit_generate',
'#submit' => array(
'::submitForm',
'::save',
),
);
return $actions;
}
/**
* Get the value for a property from the form values.
*
* This performs various processing depending on the form element type and the
* property format:
* - explode textarea values
* - filter checkboxes and store only the keys
* - recurse into compound properties
* The form build process leaves instructions for how to handle each value in
* the 'element_handling' form state setting, so that here we don't need to
* repeat the logic based on property info. Furthermore, we can't put this
* a property info array into form state storage, because it contains closures,
* which don't survive the serialization process in the database, and so the
* property info would need to be run through DCB's preparation process all
* over again.
*
* @param array $value_address
* The address array of the value in the form state values array. The final
* element of this is name of the property and the form element.
* @param $value
* The incoming form value from the form element for this property.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return
* The processed value.
*/
// TODO: check for stuff here to move to cleanUpValues().
protected function XgetFormElementValue($value_address, $value, FormStateInterface $form_state) {
// Retrieve the handling type from the form state.
$property_form_value_address_key = implode(':', $value_address);
$handling = $form_state
->get([
'element_handling',
$property_form_value_address_key,
]);
switch ($handling) {
case 'textarea':
// Array format, without options: textarea.
if (empty($value)) {
$value = [];
}
else {
// Can't split on just "\n" because for FKW reasons, linebreaks come
// back through POST as Windows-style "\r\n".
$value = preg_split("@[\r\n]+@", $value);
}
break;
case 'autocomplete':
// Array format, with extra options: textfield with autocomplete.
// Only explode a non-empty string, as explode() will turn '' into an
// array!
if (!empty($value)) {
// Textfield with autocomplete.
$value = preg_split("@,\\s*@", $value);
}
break;
case 'checkboxes':
// Array format, with options: checkboxes.
// Filter out empty values. (FormAPI *still* doesn't do this???)
$value = array_filter($value);
// Don't store values also in the keys, as some of these have dots in
// them, which ConfigAPI doesn't allow.
$value = array_keys($value);
break;
case 'compound':
// Remove the item count buttons from the values.
unset($value['actions']);
unset($value['table']);
foreach ($value as $delta => $item_value) {
$delta_value_address = $value_address;
$delta_value_address[] = $delta;
// Recurse into the child property values.
foreach ($item_value as $child_key => $child_value) {
$delta_child_value_address = $delta_value_address;
$delta_child_value_address[] = $child_key;
$value[$delta][$child_key] = $this
->getFormElementValue($delta_child_value_address, $child_value, $form_state);
}
}
break;
case 'checkbox':
case 'select':
case 'textfield':
// Nothing to do in these cases: $value is fine as it is.
break;
default:
throw new \Exception("Unknown handling type: {$handling}.");
}
return $value;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$is_new = $this->entity
->isNew();
$module = $this->entity;
// dsm($module);
$status = $module
->save();
if ($status) {
// Setting the success message.
$this
->messenger()
->addStatus($this
->t('Saved the module: @name.', array(
'@name' => $module->name,
)));
}
else {
$this
->messenger()
->addStatus($this
->t('The @name module was not saved.', array(
'@name' => $module->name,
)));
}
// Optionally advance to next tab or go to the generate page.
$element = $form_state
->getTriggeringElement();
switch ($element['#mb_action']) {
case 'submit':
$operation = $this
->getOperation();
// For a new module, we need to redirect to its edit form, as staying
// put would leave on the add form.
if ($operation == 'add') {
$operation = 'edit';
}
// For an existing module, we also redirect so that changing the machine
// name of the module goes to the new URL.
$url = $module
->toUrl($operation . '-form');
$form_state
->setRedirectUrl($url);
break;
case 'submit_next':
$next_link = $this
->getNextLink();
$url = $module
->toUrl($next_link);
$form_state
->setRedirectUrl($url);
break;
case 'submit_generate':
$url = $module
->toUrl('generate-form');
$form_state
->setRedirectUrl($url);
break;
}
}
/**
* Get the next entity link after the one for the current form.
*
* @return
* The name of an entity link.
*/
protected function getNextLink() {
// Probably a more elegant way of figuring out where we currently are
// with routes maybe?
$operation = $this
->getOperation();
// Special case for add and edit forms.
if ($operation == 'default' || $operation == 'edit') {
$operation = 'name';
}
$handler_class = $this->entityTypeManager
->getHandler('module_builder_module', 'component_sections');
$form_ops = $handler_class
->getFormOperations();
// Add in the 'name' operation, as the handler doesn't return it.
$form_ops = array_merge([
'name',
], $form_ops);
$index = array_search($operation, $form_ops);
return $form_ops[$index + 1] . '-form';
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
ComponentFormBase:: |
protected | property | The Drupal Code Builder wrapping service. | |
ComponentFormBase:: |
protected | property | The DCB Generate Task handler. | |
ComponentFormBase:: |
protected | property | The exception thrown by DCB when initialized, if any. | |
ComponentFormBase:: |
public | function |
Form constructor. Overrides EntityForm:: |
|
ComponentFormBase:: |
public static | function |
Instantiates a new instance of this class. Overrides FormBase:: |
|
ComponentFormBase:: |
protected | function | Gets the data object for the entity in the form. | |
ComponentFormBase:: |
public | function |
Sets the form entity. Overrides EntityForm:: |
|
ComponentFormBase:: |
public | function | Sets the generate task. | |
ComponentFormBase:: |
function | Construct a new form object | ||
ComponentSectionForm:: |
protected | function |
Returns an array of supported actions for the current entity form. Overrides EntityForm:: |
1 |
ComponentSectionForm:: |
public static | function | ||
ComponentSectionForm:: |
public static | function | Submission handler for the "Add another item" buttons. | |
ComponentSectionForm:: |
protected | function | Builds a form element with multiple child elements. | |
ComponentSectionForm:: |
protected | function | Builds the form element for a data item. | |
ComponentSectionForm:: |
protected | function | Builds a multi-valued form element. | |
ComponentSectionForm:: |
protected | function | Recursively clean up the submitted form values. | |
ComponentSectionForm:: |
public static | function | ||
ComponentSectionForm:: |
protected | function | Add form elements for the specified component properties. | |
ComponentSectionForm:: |
protected | function |
Copies top-level form values to entity properties Overrides EntityForm:: |
2 |
ComponentSectionForm:: |
public | function |
Gets the actual form array to be built. Overrides EntityForm:: |
3 |
ComponentSectionForm:: |
private | function | ||
ComponentSectionForm:: |
protected | function | Gets the names of properties this form should show. | 3 |
ComponentSectionForm:: |
protected | function | ||
ComponentSectionForm:: |
protected | function | Get the next entity link after the one for the current form. | |
ComponentSectionForm:: |
public static | function | Ajax callback for the item count buttons. | |
ComponentSectionForm:: |
protected static | function | Helper to remove all ajax from the form. | |
ComponentSectionForm:: |
public static | function | ||
ComponentSectionForm:: |
public static | function | Submission handler for the "Remove item" buttons. | |
ComponentSectionForm:: |
public | function |
Form submission handler for the 'save' action. Overrides EntityForm:: |
|
ComponentSectionForm:: |
public | function | Title callback. | |
ComponentSectionForm:: |
public static | function | Submission handler for the "Update variants" buttons. | |
ComponentSectionForm:: |
public static | function | Validate handler for the update variant button. | |
ComponentSectionForm:: |
public | function |
Form validation handler. Overrides FormBase:: |
1 |
ComponentSectionForm:: |
public static | function | Ajax callback for the variant buttons. | |
ComponentSectionForm:: |
public static | function | Ajax callback for the variant elements. | |
ComponentSectionForm:: |
protected | function | Set form element properties specific to array component properties. | |
ComponentSectionForm:: |
protected | function | Set form element properties specific to boolean component properties. | |
ComponentSectionForm:: |
protected | function | Set form element properties specific to compound component properties. | |
ComponentSectionForm:: |
protected | function | Set form element properties specific to array component properties. | |
ComponentSectionForm:: |
protected | function | ||
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
EntityForm:: |
protected | property | The entity being used by this form. | 7 |
EntityForm:: |
protected | property | The entity type manager. | 3 |
EntityForm:: |
protected | property | The module handler service. | |
EntityForm:: |
protected | property | The name of the current operation. | |
EntityForm:: |
private | property | The entity manager. | |
EntityForm:: |
protected | function | Returns the action form element for the current entity form. | |
EntityForm:: |
public | function | Form element #after_build callback: Updates the entity with submitted data. | |
EntityForm:: |
public | function |
Builds an updated entity object based upon the submitted form values. Overrides EntityFormInterface:: |
2 |
EntityForm:: |
public | function |
Returns a string identifying the base form. Overrides BaseFormIdInterface:: |
5 |
EntityForm:: |
public | function |
Gets the form entity. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Determines which entity will be used by this form from a RouteMatch object. Overrides EntityFormInterface:: |
1 |
EntityForm:: |
public | function |
Returns a unique string identifying the form. Overrides FormInterface:: |
10 |
EntityForm:: |
public | function |
Gets the operation identifying the form. Overrides EntityFormInterface:: |
|
EntityForm:: |
protected | function | Initialize the form state and the entity before the first form build. | 3 |
EntityForm:: |
protected | function | Prepares the entity object before the form is built first. | 3 |
EntityForm:: |
protected | function | Invokes the specified prepare hook variant. | |
EntityForm:: |
public | function | Process callback: assigns weights and hides extra fields. | |
EntityForm:: |
public | function |
Sets the entity manager for this form. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Sets the entity type manager for this form. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Sets the module handler for this form. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
Sets the operation for this form. Overrides EntityFormInterface:: |
|
EntityForm:: |
public | function |
This is the default entity object builder function. It is called before any
other submit handler to build the new entity object to be used by the
following submit handlers. At this point of the form workflow the entity is
validated and the form state… Overrides FormInterface:: |
17 |
EntityForm:: |
public | function | ||
EntityForm:: |
public | function | ||
FormBase:: |
protected | property | The config factory. | 1 |
FormBase:: |
protected | property | The request stack. | 1 |
FormBase:: |
protected | property | The route match. | |
FormBase:: |
protected | function | Retrieves a configuration object. | |
FormBase:: |
protected | function | Gets the config factory for this form. | 1 |
FormBase:: |
private | function | Returns the service container. | |
FormBase:: |
protected | function | Gets the current user. | |
FormBase:: |
protected | function | Gets the request object. | |
FormBase:: |
protected | function | Gets the route match. | |
FormBase:: |
protected | function | Gets the logger for a specific channel. | |
FormBase:: |
protected | function |
Returns a redirect response object for the specified route. Overrides UrlGeneratorTrait:: |
|
FormBase:: |
public | function | Resets the configuration factory. | |
FormBase:: |
public | function | Sets the config factory for this form. | |
FormBase:: |
public | function | Sets the request stack object to use. | |
LinkGeneratorTrait:: |
protected | property | The link generator. | 1 |
LinkGeneratorTrait:: |
protected | function | Returns the link generator. | |
LinkGeneratorTrait:: |
protected | function | Renders a link to a route given a route name and its parameters. | |
LinkGeneratorTrait:: |
public | function | Sets the link generator service. | |
LoggerChannelTrait:: |
protected | property | The logger channel factory service. | |
LoggerChannelTrait:: |
protected | function | Gets the logger for a specific channel. | |
LoggerChannelTrait:: |
public | function | Injects the logger channel factory. | |
MessengerTrait:: |
protected | property | The messenger. | 29 |
MessengerTrait:: |
public | function | Gets the messenger. | 29 |
MessengerTrait:: |
public | function | Sets the messenger. | |
RedirectDestinationTrait:: |
protected | property | The redirect destination service. | 1 |
RedirectDestinationTrait:: |
protected | function | Prepares a 'destination' URL query parameter for use with \Drupal\Core\Url. | |
RedirectDestinationTrait:: |
protected | function | Returns the redirect destination service. | |
RedirectDestinationTrait:: |
public | function | Sets the redirect destination service. | |
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. | |
UrlGeneratorTrait:: |
protected | property | The url generator. | |
UrlGeneratorTrait:: |
protected | function | Returns the URL generator service. | |
UrlGeneratorTrait:: |
public | function | Sets the URL generator service. | |
UrlGeneratorTrait:: |
protected | function | Generates a URL or path for a specific route based on the given parameters. |