Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VAGOV-TEAM-99444: Form Info page #20311

Merged
merged 36 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b43ab39
Renames start-conversion to form-name. Adjusts routing, tests, etc.
ryguyk Jan 20, 2025
b350949
Defines two routes (create, edit) for form-name page. Uses same contr…
ryguyk Jan 21, 2025
5d1fc70
Adds method to DigitalFormsService to fetch individual Digital Form n…
ryguyk Jan 21, 2025
1cb3f8e
- Moves base Form logic to FormBuilderBase. (FormBuilderNodeBase and …
ryguyk Jan 21, 2025
94c363f
Updates controller to handle loading/passing of Digital Form node whe…
ryguyk Jan 21, 2025
b07cffb
Adds entityTypeManager as explicity dependency of FormBuilderBase.
ryguyk Jan 21, 2025
cf565b1
- Sets default values on form fields when in "edit" mode.
ryguyk Jan 21, 2025
66c6c8f
Removes $nid from calls to `getFormPage`
ryguyk Jan 21, 2025
9d62ff2
- Removes FormBuilderNodeBase and FormBuilderEditBase.
ryguyk Jan 21, 2025
676b6a2
- Adds flag to indicate whether the node can be empty.
ryguyk Jan 22, 2025
008edaf
Adds loading of digital form by node id to nameAndDob controller method.
ryguyk Jan 22, 2025
7d37d58
Moves `isCreate` flag and logic from base form to FormName form, as t…
ryguyk Jan 22, 2025
6fabcd3
Sets back-button redirect to the edit page rather than create page.
ryguyk Jan 22, 2025
28eced2
Removes test classes for now-unused Form base classes. Combines tests…
ryguyk Jan 22, 2025
b2a8c2e
Removes condition to only save on updated node. Doesn't apply here (a…
ryguyk Jan 22, 2025
31376cd
Updates tests for controller.
ryguyk Jan 22, 2025
60d0718
Updates tests for FormName form.
ryguyk Jan 22, 2025
e859adf
Small update to test for back button on NameAndDobTest
ryguyk Jan 22, 2025
a8a5cfc
Removes unnecessary check for empty value.
ryguyk Jan 22, 2025
0ac61a2
Adds unit test for `getDigitalForm` method on the service.
ryguyk Jan 22, 2025
61da8f5
Changes "Form Name" to "Form Info". Updates routes, class names, etc.
ryguyk Jan 22, 2025
f6dd0c5
Updates page title and removes help text.
ryguyk Jan 22, 2025
bc14c00
Changes "Continue" button text to "Save and continue".
ryguyk Jan 22, 2025
56c4424
Change page subtitle on Form Info page.
ryguyk Jan 22, 2025
0ae5372
Removes redundant `drupalGet` callls. This is handled in the shared m…
ryguyk Jan 22, 2025
e7543b4
- Adds tests asserting correct subtitle.
ryguyk Jan 22, 2025
bc92d7a
- Removes back button on Form Info page.
ryguyk Jan 22, 2025
a08b2c8
Changes required field label to "(*Required)" rather than just an ast…
ryguyk Jan 22, 2025
a112929
Removes help text from various fields.
ryguyk Jan 22, 2025
0acc217
Configures custom template and css for Form Info page.
ryguyk Jan 22, 2025
e0a619a
Renders form via template and styles via css.
ryguyk Jan 23, 2025
429837e
Changes color on form-field hint text.
ryguyk Jan 23, 2025
b21b770
Updates test to align with updated page text.
ryguyk Jan 23, 2025
190598f
Updates LibrariesTest to check for new libraries.
ryguyk Jan 23, 2025
8a1f175
Updates ModuleTest to ensure presence of form themes.
ryguyk Jan 23, 2025
7f0adc4
Makes form elements fill form width so they all match.
ryguyk Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@
font-size: var(--vads-font-size-heading-level-6);
}

/* required fields */
.form-builder-page-container .form-item__label.form-required::after {
color: var(--vads-color-secondary-dark);
content: "(*Required)";
}

/* form field descriptions/hint text */
.form-builder-page-container .form-item__description {
color: var(--vads-color-gray-medium);
}

/* header */
.form-builder-header {
background-color: var(--va-blue-darkest);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.form-builder-page-content--form-info {
width: 335px;
}

.form-builder-page-content--form-info .form-element {
width: 100%;
}

.form-builder-content-section {
border-bottom: 1px solid var(--vads-color-base-light);
}

h3.form-builder-content-section__header {
font-size: var(--vads-font-size-heading-level-4);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
* Controller for the VA Form Builder experience.
Expand Down Expand Up @@ -40,26 +41,58 @@ class VaGovFormBuilderController extends ControllerBase {
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
private $drupalFormBuilder;
protected $drupalFormBuilder;

/**
* The Digital Forms service.
*
* @var \Drupal\va_gov_form_builder\Service\DigitalFormsService
*/
private $digitalFormsService;
protected $digitalFormsService;

/**
* The Digital Form node.
*
* When the page in question edits or references an existing
* digital form node, this property is populated. When the
* page creates a new digital form node or otherwise does
* not reference a node, this is empty.
*
* @var \Drupal\node\Entity\Node|null
*/
protected $digitalFormNode;

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
$instance = parent::create($container);

$instance->drupalFormBuilder = $container->get('form_builder');
$instance->digitalFormsService = $container->get('va_gov_form_builder.digital_forms_service');

return $instance;
}

/**
* Loads and sets the Digital Form node.
*
* @param string $nid
* The node id to load.
*
* @return bool
* TRUE if successfully loaded. FALSE otherwise.
*/
protected function loadDigitalFormNode($nid) {
$digitalFormNode = $this->digitalFormsService->getDigitalForm($nid);
if (!empty($digitalFormNode)) {
$this->digitalFormNode = $digitalFormNode;
return TRUE;
}

return FALSE;
}

/**
* Returns a render array representing the page with the passed-in content.
*
Expand All @@ -71,7 +104,7 @@ public static function create(ContainerInterface $container) {
* Libraries for the page, in addition to the Form Builder general library,
* which is added automatically.
*/
private function getPage($pageContent, $subtitle, $libraries = NULL) {
protected function getPage($pageContent, $subtitle, $libraries = NULL) {
$page = [
'#type' => 'page',
'content' => $pageContent,
Expand Down Expand Up @@ -106,12 +139,10 @@ private function getPage($pageContent, $subtitle, $libraries = NULL) {
* @param string $libraries
* Libraries for the page, in addition to the Form Builder general library,
* which is added automatically.
* @param string $nid
* The node id, passed in when the form in question edits an existing node.
*/
private function getFormPage($formName, $subtitle, $libraries = NULL, $nid = NULL) {
protected function getFormPage($formName, $subtitle, $libraries = NULL) {
// @phpstan-ignore-next-line
$form = $this->drupalFormBuilder->getForm('Drupal\va_gov_form_builder\Form\\' . $formName, $nid);
$form = $this->drupalFormBuilder->getForm('Drupal\va_gov_form_builder\Form\\' . $formName, $this->digitalFormNode);

return $this->getPage($form, $subtitle, $libraries);
}
Expand Down Expand Up @@ -141,7 +172,7 @@ public function home() {

$pageContent = [
'#theme' => self::PAGE_CONTENT_THEME_PREFIX . 'home',
'#build_form_url' => Url::fromRoute('va_gov_form_builder.start_conversion')->toString(),
'#build_form_url' => Url::fromRoute('va_gov_form_builder.form_info.create')->toString(),
'#recent_forms' => $recentForms,
];
$subtitle = 'Select a form';
Expand All @@ -151,12 +182,25 @@ public function home() {
}

/**
* Start-conversion page.
* Form-info page.
*
* @param string $nid
* The node id, passed in when the page edits an existing node.
*/
public function startConversion() {
$formName = 'StartConversion';
$subtitle = 'Subtitle Placeholder';
return $this->getFormPage($formName, $subtitle);
public function formInfo($nid = NULL) {
$formName = 'FormInfo';
$subtitle = 'Build a form';
$libraries = ['form_info'];

if (!empty($nid)) {
// This is an edit.
$nodeFound = $this->loadDigitalFormNode($nid);
if (!$nodeFound) {
throw new NotFoundHttpException();
}
}

return $this->getFormPage($formName, $subtitle, $libraries);
}

/**
Expand All @@ -165,7 +209,11 @@ public function startConversion() {
public function nameAndDob($nid) {
$formName = 'NameAndDob';
$subtitle = 'Subtitle Placeholder';
return $this->getFormPage($formName, $subtitle, NULL, $nid);
$nodeFound = $this->loadDigitalFormNode($nid);
if (!$nodeFound) {
throw new NotFoundHttpException();
}
return $this->getFormPage($formName, $subtitle);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,185 @@

namespace Drupal\va_gov_form_builder\Form\Base;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Abstract base class for Form Builder form steps.
*/
abstract class FormBuilderBase extends FormBase {

/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;

/**
* The Digital Form node created or loaded by this form step.
*
* @var \Drupal\node\Entity\Node
*/
protected $digitalFormNode;

/**
* Flag indicating if the node has been changed.
*
* Indicates if the node has been changed
* since the form was first instantiated.
*
* @var bool
*/
protected $digitalFormNodeIsChanged;

/**
* Flag indicating whether this form allows an empty node.
*
* This defaults to FALSE. The only time an empty node
* should be allowed is on the form that creates
* the node for the first time. Every other form should
* operate on an existing form and should require a
* node to be populated.
*
* @var bool
*/
protected $allowEmptyDigitalFormNode;

/**
* {@inheritDoc}
*/
public function __construct(EntityTypeManagerInterface $entityTypeManager) {
$this->entityTypeManager = $entityTypeManager;
$this->allowEmptyDigitalFormNode = FALSE;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')
);
}

/**
* Returns the Digital Form fields accessed by this form step.
*/
abstract protected function getFields();

/**
* Sets (creates or updates) a Digital Form node from the form-state data.
*/
abstract protected function setDigitalFormNodeFromFormState(array &$form, FormStateInterface $form_state);

/**
* Returns a field value from the Digital Form node.
*
* If Digital Form node is not set, or `fieldName`
* does not exist, returns NULL. This is primarily
* used to populate forms with default values when the
* form edits an existing Digital Form node.
*
* @param string $fieldName
* The name of the field whose value should be fetched.
*/
protected function getDigitalFormNodeFieldValue($fieldName) {
if (empty($this->digitalFormNode)) {
return NULL;
}

try {
if ($fieldName === 'title') {
return $this->digitalFormNode->getTitle();
}

return $this->digitalFormNode->get($fieldName)->value;
}
catch (\Exception $e) {
return NULL;
}
}

/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $node = NULL) {
// When form is first built, initialize flag to false.
$this->digitalFormNodeIsChanged = FALSE;

if (empty($node) && !$this->allowEmptyDigitalFormNode) {
throw new \InvalidArgumentException('Digital Form node cannot be null.');
}
$this->digitalFormNode = $node;

return $form;
}

/**
* Determines if `digitalFormNode` has a chapter (paragraph) of a given type.
*
* @param string $type
* The chapter (paragraph) type.
*
* After initially containing some logic, this function
* is now empty, and this entire class is a candiate
* for removal. Leaving it here for now, as it might prove
* necessary as we continue on.
Comment on lines -16 to -19
Copy link
Contributor Author

@ryguyk ryguyk Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This base class was initially used, then went through a time where the only logic it had was removed. Now, the need for two layers here (three separate base classes) has gone away, and this class is the only base class, so a lot of the logic from the others has been moved in here and this class has gotten new life.

* @return bool
* TRUE if the chapter exists; FALSE if the chapter
* does not exist or the node does not exist.
*/
protected function digitalFormNodeHasChapterOfType($type) {
if (empty($this->digitalFormNode)) {
return FALSE;
}

$chapters = $this->digitalFormNode->get('field_chapters')->getValue();

foreach ($chapters as $chapter) {
if (isset($chapter['target_id'])) {
$paragraph = $this->entityTypeManager->getStorage('paragraph')->load($chapter['target_id']);
if ($paragraph) {
if ($paragraph->bundle() === $type) {
return TRUE;
}
}
}
}

return FALSE;
}

/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$this->setDigitalFormNodeFromFormState($form, $form_state);

// Validate the node entity.
/** @var \Symfony\Component\Validator\ConstraintViolationListInterface $violations */
$violations = $this->digitalFormNode->validate();

// Loop through each violation and set errors on the form.
if ($violations->count() > 0) {
foreach ($violations as $violation) {
// Account for nested property path(e.g. `field_omb_number.0.value`).
$fieldName = explode('.', $violation->getPropertyPath())[0];

// Only concern ourselves with validation of fields used on this form.
if (in_array($fieldName, $this->getFields())) {
$message = $violation->getMessage();
$form_state->setErrorByName($fieldName, $message);
}
}
}
}

/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
public function submitForm(array &$form, FormStateInterface $form_state) {
// Save the previously validated node.
$this->digitalFormNode->save();
}

}
Loading
Loading