Commit 1c72fda1 authored by alexpott's avatar alexpott

Issue #2448357 by amateescu: Config entity forms need to have access to an...

Issue #2448357 by amateescu: Config entity forms need to have access to an updated entity object at all times
parent 9c449689
......@@ -50,6 +50,12 @@ public static function create(ContainerInterface $container) {
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
// Content entity forms do not use the parent's #after_build callback
// because they only need to rebuild the entity in the validation and the
// submit handler because Field API uses its own #after_build callback for
// its widgets.
unset($form['#after_build']);
$this->getFormDisplay($form_state)->buildForm($this->entity, $form, $form_state);
// Allow modules to act before and after form language is updated.
$form['#entity_builders']['update_form_langcode'] = [$this, 'updateFormLangcode'];
......
......@@ -131,13 +131,13 @@ protected function init(FormStateInterface $form_state) {
/**
* Returns the actual form array to be built.
*
* @see \Drupal\Core\Entity\EntityForm::build()
* @see \Drupal\Core\Entity\EntityForm::processForm()
* @see \Drupal\Core\Entity\EntityForm::afterBuild()
*/
public function form(array $form, FormStateInterface $form_state) {
$entity = $this->entity;
// Add a process callback.
// Add #process and #after_build callbacks.
$form['#process'][] = '::processForm';
$form['#after_build'][] = '::afterBuild';
return $form;
}
......@@ -155,6 +155,23 @@ public function processForm($element, FormStateInterface $form_state, $form) {
return $element;
}
/**
* Form element #after_build callback: Updates the entity with submitted data.
*
* Updates the internal $this->entity object with submitted values when the
* form is being rebuilt (e.g. submitted via AJAX), so that subsequent
* processing (e.g. AJAX callbacks) can rely on it.
*/
public function afterBuild(array $element, FormStateInterface $form_state) {
// Rebuild the entity if #after_build is being called as part of a form
// rebuild, i.e. if we are processing input.
if ($form_state->isProcessingInput()) {
$this->entity = $this->buildEntity($element, $form_state);
}
return $element;
}
/**
* Returns the action form element for the current entity form.
*/
......
......@@ -801,8 +801,8 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
// The #after_build flag allows any piece of a form to be altered
// after normal input parsing has been completed.
if (isset($element['#after_build']) && !isset($element['#after_build_done'])) {
foreach ($element['#after_build'] as $callable) {
$element = call_user_func_array($callable, array($element, &$form_state));
foreach ($element['#after_build'] as $callback) {
$element = call_user_func_array($form_state->prepareCallback($callback), array($element, &$form_state));
}
$element['#after_build_done'] = TRUE;
}
......
......@@ -319,6 +319,44 @@ function testCRUDUI() {
$this->drupalPostForm('admin/structure/config_test/manage/0/delete', array(), 'Delete');
$this->assertFalse(entity_load('config_test', '0'), 'Test entity deleted');
// Create a configuration entity with a property that uses AJAX to show
// extra form elements.
$this->drupalGet('admin/structure/config_test/add');
// Test that the dependent element is not shown initially.
$this->assertFieldByName('size');
$this->assertNoFieldByName('size_value');
$id = strtolower($this->randomMachineName());
$edit = [
'id' => $id,
'label' => $this->randomString(),
'size' => 'custom',
];
$this->drupalPostAjaxForm(NULL, $edit, 'size');
// Check that the dependent element is shown after selecting a 'size' value.
$this->assertFieldByName('size');
$this->assertFieldByName('size_value');
// Test the same scenario but it in a non-JS case by using a 'js-hidden'
// submit button.
$this->drupalGet('admin/structure/config_test/add');
$this->assertFieldByName('size');
$this->assertNoFieldByName('size_value');
$this->drupalPostForm(NULL, $edit, 'Change size');
$this->assertFieldByName('size');
$this->assertFieldByName('size_value');
// Submit the form with the regular 'Save' button and check that the entity
// values are correct.
$edit += ['size_value' => 'medium'];
$this->drupalPostForm(NULL, $edit, 'Save');
$entity = entity_load('config_test', $id);
$this->assertEqual($entity->get('size'), 'custom');
$this->assertEqual($entity->get('size_value'), 'medium');
}
}
......@@ -68,6 +68,8 @@ function testImport() {
'label' => 'New',
'weight' => 0,
'style' => '',
'size' => '',
'size_value' => '',
'protected_property' => '',
);
$staging->write($dynamic_name, $original_dynamic_data);
......@@ -373,7 +375,9 @@ function testImportErrorLog() {
'label' => 'Primary',
'weight' => 0,
'style' => NULL,
'protected_property' => null,
'size' => NULL,
'size_value' => NULL,
'protected_property' => NULL,
);
$staging->write($name_primary, $values_primary);
$values_secondary = array(
......@@ -388,7 +392,9 @@ function testImportErrorLog() {
'label' => 'Secondary Sync',
'weight' => 0,
'style' => NULL,
'protected_property' => null,
'size' => NULL,
'size_value' => NULL,
'protected_property' => NULL,
);
$staging->write($name_secondary, $values_secondary);
// Verify that there are configuration differences to import.
......
......@@ -173,6 +173,8 @@ function testNew() {
'label' => 'New',
'weight' => 0,
'style' => '',
'size' => '',
'size_value' => '',
'protected_property' => '',
);
$staging->write($dynamic_name, $original_dynamic_data);
......
......@@ -15,6 +15,12 @@ config_test_dynamic:
style:
type: string
label: 'style'
size:
type: string
label: 'Size'
size_value:
type: string
label: 'Size value'
protected_property:
type: string
label: 'Protected property'
......
......@@ -54,6 +54,50 @@ public function form(array $form, FormStateInterface $form_state) {
$form['style']['#options'] = image_style_options();
}
// The main premise of entity forms is that we get to work with an entity
// object at all times instead of checking submitted values from the form
// state.
$size = $entity->get('size');
$form['size_wrapper'] = array(
'#type' => 'container',
'#attributes' => array(
'id' => 'size-wrapper',
),
);
$form['size_wrapper']['size'] = array(
'#type' => 'select',
'#title' => 'Size',
'#options' => array(
'custom' => 'Custom',
),
'#empty_option' => '- None -',
'#default_value' => $size,
'#ajax' => array(
'callback' => '::updateSize',
'wrapper' => 'size-wrapper',
),
);
$form['size_wrapper']['size_submit'] = array(
'#type' => 'submit',
'#value' => t('Change size'),
'#attributes' => array(
'class' => array('js-hide'),
),
'#submit' => array(array(get_class($this), 'changeSize')),
);
$form['size_wrapper']['size_value'] = array(
'#type' => 'select',
'#title' => 'Custom size value',
'#options' => array(
'small' => 'Small',
'medium' => 'Medium',
'large' => 'Large',
),
'#default_value' => $entity->get('size_value'),
'#access' => !empty($size),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
......@@ -67,6 +111,20 @@ public function form(array $form, FormStateInterface $form_state) {
return $form;
}
/**
* Ajax callback for the size selection element.
*/
public static function updateSize(array $form, FormStateInterface $form_state) {
return $form['size_wrapper'];
}
/**
* Element submit handler for non-JS testing.
*/
public static function changeSize(array $form, FormStateInterface $form_state) {
$form_state->setRebuild();
}
/**
* {@inheritdoc}
*/
......
......@@ -92,7 +92,8 @@ function editor_form_filter_admin_overview_alter(&$form, FormStateInterface $for
function editor_form_filter_format_form_alter(&$form, FormStateInterface $form_state) {
$editor = $form_state->get('editor');
if ($editor === NULL) {
$format_id = $form_state->getFormObject()->getEntity()->id();
$format = $form_state->getFormObject()->getEntity();
$format_id = $format->isNew() ? NULL : $format->id();
$editor = editor_load($format_id);
$form_state->set('editor', $editor);
}
......@@ -175,8 +176,9 @@ function editor_form_filter_admin_format_editor_configure($form, FormStateInterf
$form_state->set('editor', FALSE);
}
elseif (empty($editor) || $editor_value !== $editor->getEditor()) {
$format = $form_state->getFormObject()->getEntity();
$editor = entity_create('editor', array(
'format' => $form_state->getFormObject()->getEntity()->id(),
'format' => $format->isNew() ? NULL : $format->id(),
'editor' => $editor_value,
));
$form_state->set('editor', $editor);
......@@ -215,7 +217,8 @@ function editor_form_filter_admin_format_validate($form, FormStateInterface $for
*/
function editor_form_filter_admin_format_submit($form, FormStateInterface $form_state) {
// Delete the existing editor if disabling or switching between editors.
$format_id = $form_state->getFormObject()->getEntity()->id();
$format = $form_state->getFormObject()->getEntity();
$format_id = $format->isNew() ? NULL : $format->id();
$original_editor = editor_load($format_id);
if ($original_editor && $original_editor->getEditor() != $form_state->getValue(array('editor', 'editor'))) {
$original_editor->delete();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment