Hi,
Background
I’m trying to create a Package that lets users create comments. That works more or less.
What I try to do
Validating user input that come from a <form>
with a Controller/Validator. Validation works insofar as no comment gets created when there is invalid user input.
The problem
If there’s invalid user input showing the validation results / flash messages goes crazy for some reason. No comment gets created and therefore no new comment will be rendered. That’s fine. But the user should get some feedback. Unfortunately the feedback doesn’t get rendered, when the user gets redirected back to the form—and I don’t know why. After refreshing the page containing the form or browsing to anther documentNode and back lets Neos render the messages. But I don’t get when exactly it is the case. If the messages get rendered I can refresh/reload the page and browse to another documentNode and back it the messages are still there and just disappear as magically as they came. (I don’t navigate back and forth in the browsers cache but really reload the page or change documentNodes clicking links)
How I’m trying to do it (a.k.a. the code)
The form
<!--F:FLASHMESSAGES-->
<f:flashMessages as="flashMessages">
<f:for each="{flashMessages}" as="message">
<div class="message">
<div class="header">
{message.title}
</div>
<p>{message}</p>
</div>
</f:for>
</f:flashMessages>
<!--F:VALIDATION.RESULTS-->
<f:validation.results>
<ul>
<f:for each="{validationResults.flattenedErrors}" as="errors" key="propertyPath">
<li>{propertyPath}
<ul>
<f:for each="{errors}" as="error">
<li>{error.code}: {error}</li>
</f:for>
</ul>
</li>
</f:for>
</ul>
</f:validation.results>
<!--F:FORM-->
<f:form class="ui reply form" action="create" controller="Comment" package="V.S" objectName="comment" id="newCommentForm">
<f:form.hidden name="feedNode" value="{node.path}" />
<f:form.textfield property="authorName" id="authorName" placeholder="Name"/>
<f:form.textfield property="authorEmail" id="authorEmail" placeholder="E-Mail"/>
<f:form.textarea property="commentContent" id="commentContent" />
<f:form.button type="submit" class="ui blue labeled submit icon button">
<i class="icon edit"></i> Publish
</f:form.button>
</f:form>
The Controller
class CommentController extends ActionController
{
/**
* Creates a new comment
*
* @Flow\Validate(type="V.S:Comment", value="comment")
*
* @param NodeTemplate<V.S:Comment> $comment NodeTemplate holding the user input as properties
* @param NodeInterface $feedNode The node which contains the comment feed's ContentCollection that will contain the new comment node
* @return void
*/
public function createAction(NodeTemplate $comment, NodeInterface $feedNode)
{
// @var commentServie: creates the comment. It basically modifies some properties of the NodeTemplate and calls createNodeFromTemplate to create a child node in feedNode. Nothing special thats why I omit it here.
$commentNode = $this->commentService->create($comment, $feedNode);
// See Neos\Neos\View\FusionView where this method is stolen from ;)
$commentDocumentNode = $this->getClosestDocumentNode($commentNode);
$this->addFlashMessage('Success', 'Comment created. Thank you!');
# Send signal and redirect user
#------------------------------
$this->emitCommentCreated($commentNode, $feedNode);
$this->redirectToNewComment($commentDocumentNode, $commentNode->getName());
}
protected function redirectToNewComment(NodeInterface $targetDocumentNode, $anchor)
{
# Add the comment name as page anchor to the URI
$this->uriBuilder->setSection($anchor);
# Build the URI to the Frontend/NodeController which shows the documentNode
$uri = $this->uriBuilder->uriFor('show', array('node' => $targetDocumentNode), 'Frontend\Node', 'Neos.Neos');
$this->redirectToUri($uri);
}
protected function emitCommentCreated(NodeInterface $commentNode, NodeInterface $feedNode)
{
}
}
The Validator
/**
* Basically this a modification of GenericObjectValidator to work with nodes.
*/
class CommentValidator extends GenericObjectValidator
{
/**
* @var ValidatorResolver
* @Flow\Inject
*/
protected $validatorResolver;
/**
* Checks if the given value is valid according to the validator, and returns
* the Error Messages object which occurred.
*
* @param mixed $value The value that should be validated
* @return ErrorResult
*/
public function validate($value)
{
$this->result = new ErrorResult();
if ($this->acceptsEmptyValues === false || $this->isEmpty($value) === false) {
if (!is_object($value)) {
$this->addError('Object expected, %1$s given.', 1241099149, [gettype($value)]);
} elseif ($this->isValidatedAlready($value) === false) {
# Create an add propertyValidators
#---------------------------------
$this->addPropertyValidator('authorName', $this->validatorResolver->createValidator('String'));
$this->addPropertyValidator('authorName', $this->validatorResolver->createValidator('NotEmpty'));
$this->addPropertyValidator('authorName', $this->validatorResolver->createValidator('StringLength',array('minimum' => 3)));
$this->addPropertyValidator('authorEmail', $this->validatorResolver->createValidator('NotEmpty'));
$this->addPropertyValidator('authorEmail', $this->validatorResolver->createValidator('EmailAddress'));
$this->addPropertyValidator('commentContent', $this->validatorResolver->createValidator('String'));
$this->addPropertyValidator('commentContent', $this->validatorResolver->createValidator('NotEmpty'));
$this->addPropertyValidator('commentContent', $this->validatorResolver->createValidator('StringLength',array('minimum' => 3)));
$this->isValid($value);
}
}
return $this->result;
}
/**
* Load the property value to be used for validation.
*
* @param object $object
* @param string $propertyName
* @return mixed
*/
protected function getPropertyValue($node, $propertyName)
{
return $node->getProperty($propertyName);
}
}
Routes.yaml
-
name: 'Add a new comment'
uriPattern: 'comment/create.html'
defaults:
'@package': 'V.S'
'@controller': 'Comment'
'@format': 'html'
'@action': 'create'
The Flow manual states
When validation in the MVC layer happens, it is possible to handle errors correctly. In a nutshell, the process is as follows:
- …
- if there is a property mapping or validation error, the last page (which usually contains an edit-form) is re-displayed, an error message is shown and the erroneous field is highlighted.
If I submit the form with incorrect values the last page will be rendered but the URI doesn’t fit. Instead of something like http://127.0.0.1:8081/path/to/documentNode.html
(where the form is) the URI is: http://127.0.0.1:8081/comment/create.html
.
Could this cause the problem? Even if not: how can I avoid this (because it looks really bad).
Screenshot
Here’s a Screenshot that may help understand it: These are all flash messages. The first five were created because validation failed but not rendered. After creating a valid comment all of the messages get rendered. Now I can browse/reload as much as I want and the messages keep appearing for some time. The validation results are missing completely!
Any help is more than welcome
Edit:
One more thing: If i submited an invalid comment and got redirected to http://127.0.0.1:8081/comment/create.html
and go immediately to http://127.0.0.1:8081/neos
for logging in into the backend the following happens:
Can someone explain why the error gets rendered in the login screen but not on the page?
Edit 2:
Could this be a cache problem? If I submit invalid value, change something negligible in the fluid layout file, save it and reload, the validation results/flashmessages will get rendered.