action parameter not populated by fluid form - typo3

Hello all I have trouble providing an object filled in a form to a an action: I'm able to load the form and populate it using the object prepared in the showing action but when I submit it to the form to store the object the form content is not passed in the action method parameter , but instead is in an array in the request arguments. So it seems the model class is correct, the form is correct and the first action is correct, but either there is something missing in the fluid form or something is wrong in the action. Any suggestion on what to look for as cause of the issue? Thanks in advance.
This is the controller that loads the form
/**
* #param \Aip\AipMediakey\Domain\Model\Subscriptions $newSubscription
* #throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException
* #throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
* #throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
*/
public function iscrizioneAwardFirstStepAction(\Aip\AipMediakey\Domain\Model\Subscriptions $newSubscription = NULL)
{
.....
$newSubscription = $this->objectManager->get('Aip\AipMediakey\Domain\Model\Subscriptions');
/** #var \Aip\AipMediakey\Domain\Model\Subscriptions $newSubscription */
$newSubscription->setCampaignTitle('');
$newSubscription->setProductName('');
$this->view->assign('newSubscription', $newSubscription);
.....
This is the fluid form
<f:form action="iscrizioneAwardStepSoggetti" object="{newSubscription}" name="newSubscription" >
<f:form.hidden property="eventId" value="{award.uid}"/>
Iscrizione all'award : {award.title}
Campagna <br>
Titolo Campagna <f:form.textfield property="campaignTitle" /> <br>
NomeProdotto <f:form.textfield property="productName" /> <br>
<f:form.button type="submit" name="Indietro" value="indietro" formmethod="post">Indietro</f:form.button>
<f:form.button type="submit" name="Procedi" value="procedi" formmethod="post">Procedi</f:form.button>
</f:form>
This the action that is called submitting the form:
/**
* #param \Aip\AipMediakey\Domain\Model\Subscriptions $newSubscription
* #throws \TYPO3\CMS\Extbase\Mvc\Exception\StopActionException
* #throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
* #throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException
*/
public function IscrizioneAwardStepSoggettiAction(\Aip\AipMediakey\Domain\Model\Subscriptions $newSubscription = NULL)
{
$this->subscriptionsRepository->add($newSubscription);
}

I have found my mistake, the method
IscrizioneAwardStepSoggettiAction
should have been named
iscrizioneAwardStepSoggettiAction (starting i in lowercase)
as stated in ext_local_conf.php
I hope at least this answer can help someone else.

Related

How to get fal object instead of array when using flux:field.inline.fal

In my flux content template configuration section I define an image field like this:
<flux:field.inline.fal label="Image" name="images" maxItems="1" minItems="0" showThumbs="1"/>
In my flux content template main section I debug the output:
<f:for each="{v:resource.record.fal(table: 'tt_content',field: 'images', uid: '{record.uid}')}" as="image" iteration="iterator">
<f:debug>{image}</f:debug>
</f:for>
The debuging output shows an array but what I need is the FAL object of that image I added in the backend.
I googled a lot and found some older posts from 2015. All say it is not possible to get the fal object(!) in flux. Is it still true? Do you know any way?
One solution is to create a custom ViewHelper:
<?php
namespace Cmichael\Typo3projectprovider\ViewHelpers\Content;
/* FalViewHelper
* To get fal object by image uid (respectivly in flux templates)
* Usage example: <cm:content.fal referenceUid="{image.uid}">
* */
class FalViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* #var bool
*/
protected $escapeOutput = false;
/**
* Initialize arguments.
*
*/
public function initializeArguments() {
$this->registerArgument('referenceUid', 'integer', 'File reference uid', true, 0);
}
/**
* Return file reference
*
* #return \TYPO3\CMS\Core\Resource\FileReference|null
*/
public function render() {
$referenceUid = $this->arguments['referenceUid'];
$fileReferenceData = $GLOBALS['TSFE']->sys_page->checkRecord('sys_file_reference', $referenceUid);
return $fileReferenceData ? \TYPO3\CMS\Core\Resource\ResourceFactory::getInstance()->getFileReferenceObject($referenceUid) : $referenceUid;
}
}

TYPO3 Extension developement: Attached objects of a relation aren't edited when the main record is

So currently i'm working on a TYPO3 extension using a Relation (1:n) to a specific class that provides objects to the main class. Let me explain a bit more in detail: I have a class, that next to normal properties, has a relation to a class that provides objects for some kind of checklist. The idea is that the FE user should be able to add new checklist entries, that's why i use that kind of concept. Initially it would look like this for the edit Action (the [x] represent that it's just a checklist):
main class
property of main class of type boolean [x]
attached object with a string and a boolean property [x]
attached object with a string and a boolean property [x]
property of main class of type text (for comments)
The FE user should be able to edit the record, so he should set the checklist entires to true or false, but even though the checklist entries appear, even those of the attached objects, they aren't persisted, only the properties of the main class are persisted when the FE user edits the records in the FE.
Here is my code:
The domain model of the main class (just the lines that specify the relation to the class that "provides" the objects). Keep in mind that few objects of the related class are attached to the main class when a record of the main class is created, the specific code just isn't in the main classe's controller...
/**
* zugewCheckobject
*
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\...\Kundentermine\Domain\Model\Checkobject>
* #cascade remove
*/
protected $zugewCheckobject = null;
public function __construct()
{
$this->zugewCheckobject = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Initializes all ObjectStorage properties
* Do not modify this method!
* It will be rewritten on each save in the extension builder
* You may modify the constructor of this class instead
*
* #return void
*/
protected function initStorageObjects()
{
$this->zugewCheckobject = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Adds a Checkobject
*
* #param \...\Kundentermine\Domain\Model\Checkobject $zugewCheckobject
* #return void
*/
public function addZugewCheckobject(\...\Kundentermine\Domain\Model\Checkobject $zugewCheckobject)
{
$this->zugewCheckobject->attach($zugewCheckobject);
}
/**
* Removes a Checkobject
*
* #param \...\Kundentermine\Domain\Model\Checkobject $zugewCheckobjectToRemove The Checkobject to be removed
* #return void
*/
public function removeZugewCheckobject(\...\Kundentermine\Domain\Model\Checkobject $zugewCheckobjectToRemove)
{
$this->zugewCheckobject->detach($zugewCheckobjectToRemove);
}
/**
* Returns the zugewCheckobject
*
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\...\Kundentermine\Domain\Model\Checkobject> $zugewCheckobject
*/
public function getZugewCheckobject()
{
return $this->zugewCheckobject;
}
/**
* Sets the zugewCheckobject
*
* #param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Unilab\Kundentermine\Domain\Model\Checkobject> $zugewCheckobject
* #return void
*/
public function setZugewCheckobject(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $zugewCheckobject)
{
$this->zugewCheckobject = $zugewCheckobject;
}
The edit and update Actions in the controller of the main class...
public function editAction(\...\Kundentermine\Domain\Model\Kaufmnnisch $kaufmnnisch)
{
$this->view->assign('kaufmnnisch', $kaufmnnisch);
}
/**
* action update
*
* #param \...\Kundentermine\Domain\Model\Kaufmnnisch $kaufmnnisch
* #return void
*/
public function updateAction(\...\Kundentermine\Domain\Model\Kaufmnnisch $kaufmnnisch)
{
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\CMS\Extbase\Object\ObjectManager');
$checkobjectRepository = $objectManager->get(\...\Kundentermine\Domain\Repository\CheckobjectRepository::class);
foreach($kaufmnnisch->getZugewCheckobject() as $chk){
$checkobjectRepository->update($chk);
}
$this->kaufmnnischRepository->update($kaufmnnisch);
$this->redirect('list');
}
Part of the "edit" template. I pass the object "kaufmnnisch" as arguments to a partial of the class Chekobject (as it has objects of that class attached to it) and of course to a partial of the class Kaufmnnisch...
<f:section name="main">
<h1>Edit Kaufmnnisch</h1>
<f:flashMessages />
<f:render partial="FormErrors" arguments="{object:Kaufmnnisch}" />
<f:form action="update" name="kaufmnnisch" object="{kaufmnnisch}" >
<f:render partial="Checkobject/FormFields" arguments="{kaufmnnisch:kaufmnnisch}" />
<f:render partial="Kaufmnnisch/FormFields" arguments="{kaufmnnisch:kaufmnnisch}" />
<f:form.submit value="Save" />
</f:form>
</f:section>
Again: The problem is that only the properties of the main class get edited not those of the related objects. My assumption is that the Edit template or the partials aren't correctly set.
Edit: Here is the code for the partial of the Checkobject class that gets the object Kaufmnnisch as an argument passed.
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:for each="{kaufmnnisch.zugewcheckobject}" as="chk" >
<f:form.checkbox property="checked" value="0" /><br />
</f:for>
</html>
So basically "zugewcheckobject" is my object storage. Each checkbox gets rendered in the Edit template (thanks to the for each viewhelper), but the values for the boolean properties of each Checkobject object aren't persisted if the user clicks on the "save" button.
Edit2: A var_dump within the updateAction (within the foreach loop) reveals that the "Checked" property of each Checkobject object isn't changed, so it's not only not persisted, but the changed values aren't passed from the Edit template to the updateAction.
Edit3: Acessing every Checkobject object (here: $chk) via the objectstorage "zugewcheckobject" of the passed object of the class "Kaufmnnisch" in the Edit template is possible though, i just cant change the specific properties of the Checkobject objects, because JUST the Kaufmnnisch object is passed back to the updateAction of the specific controller, not each Checkobject of the objectstorage ("zugewcheckobject").
As i understand an objectstorage just contains references to its corresponding objects, not the objects themselfs, negating any changes that were made to them in the view.
In short: How can i also pass the corresponding objectstorage objects to the updateAction in one go for them to be persisted?
Edit4: It also doesn't seem to work with that solution...
<f:form action="update" name="kaufmnnisch" object="{kaufmnnisch}" >
<f:for each="{kaufmnnisch.zugewCheckobject}" as="chk" key="chkIndex" >
<f:form.hidden property="zugewCheckobject.{chkIndex}.__identity" value="{chk.uid}" />
<f:form.checkbox property="zugewCheckobject.{chkIndex}.checked" value="{chk.checked}" /><br />
</f:for>
<f:render partial="Kaufmnnisch/FormFields" arguments="{kaufmnnisch:kaufmnnisch}" />
<f:form.submit value="Save" />
</f:form>
The problem was solved here: TYPO3 extension thought experiment: Editing a news entry and the comment entries in one go?

Add/Create Element to ObjectStorage using Javascript in Extbase/Fluid TYPO3

what is the correct way to dynamically create new Child Elements in a Fluid Form using JavaScript?
Problem:
1:n Relation (Parent/Child) using Extbase ObjectStorages:
When the Parent Fluid Form is called it should be possible to add several childs (incl. properties of course!)
Dirty, partly working, Solution:
I added some JS Code and added the required input elements dynamically.
The "xxx" will be interated for each Child. The data will be correctly stored in the DB.
<input type="text" placeholder="First Name" name="tx_booking[newBooking][accompanyingperson][xxx][firstname]">
However, if an error occurres all child forms disappear and no f3-form-error will be shown. The reason for this, may be the redirect to originalRequest (initial form without child fields).
How can I handle this Problem without dirty tricks?
Please give me shirt hint.
Again, I will answer the question myself!
The following lines are the foulest code ever but it works.
I really want to know how to do this in a correct way. However, the solution is, to get the Arguments from the dynamically added JS Inputs. This is done in the errorAction and will be passed by the forward() to the initial Action, where the Errors should be appear.
I for all think, that must be a better way by using the PropertyMapper and modify the trustedProperties....
Here a short example:
// Error function in Controller
protected function errorAction() {
$referringRequest = $this->request->getReferringRequest();
// Manual added JS Data
if($this->request->hasArgument('newRegistration'))
{
$newRegistration = $this->request->getArgument('newRegistration');
$referringRequest->setArgument('accompanyingperson', $newRegistration['accompanyingperson']);
}
if ($referringRequest !== NULL) {
$originalRequest = clone $this->request;
$this->request->setOriginalRequest($originalRequest);
$this->request->setOriginalRequestMappingResults($this->arguments->getValidationResults());
$this->forward($referringRequest->getControllerActionName(), $referringRequest->getControllerName(), $referringRequest->getControllerExtensionName(), $referringRequest->getArguments());
}
}
// action new in Controller
public function newAction(\***\***\Domain\Model\Registration $newRegistration = NULL) {
if($this->request->hasArgument('accompanyingperson'))
{
$this->view->assign('accPer', $this->request->getArgument('accompanyingperson'));
}
.
.
.
}
//Fluid template of Action New
<f:if condition="{accPer}">
<f:for each="{accPer}" as="ap" key="key" iteration="i">
<f:form.textfield class="form-control" placeholder="First Name" property="accompanyingperson.{key}.firstname"/>
.
.
.
</f:for>
</f:if>
Following is my solution, something like yours.
Models
class Resume extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
/**
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<Builder>
* #cascade remove
*/
protected $builders;
}
class Builder extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
/**
* #var string
*/
protected $title;
}
Controller
class ResumeController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/**
* #var \Dagou\Resume\Domain\Repository\ResumeRepository
* #inject
*/
protected $resumeRepository;
/**
* #param \Dagou\Resume\Domain\Model\Resume $resume
* #see \Dagou\Resume\Controller\ResumeController::saveAction()
*/
protected function createAction(\Dagou\Resume\Domain\Model\Resume $resume = NULL) {
$this->view->assignMultiple([
'resume' => $resume,
]);
}
protected function initializeCreateAction() {
if (($request = $this->request->getOriginalRequest())) {
$this->request->setArgument('resume', $request->getArgument('resume'));
$propertyMappingConfiguration = $this->arguments->getArgument('resume')->getPropertyMappingConfiguration();
$propertyMappingConfiguration->allowCreationForSubProperty('builders.*');
$propertyMappingConfiguration->allowProperties('builders')
->forProperty('builders')->allowAllProperties()
->forProperty('*')->allowAllProperties();
}
}
protected function initializeSaveAction() {
$propertyMappingConfiguration = $this->arguments->getArgument('resume')->getPropertyMappingConfiguration();
$propertyMappingConfiguration->allowCreationForSubProperty('builders.*');
$propertyMappingConfiguration->allowProperties('builders')
->forProperty('builders')->allowAllProperties()
->forProperty('*')->allowAllProperties();
}
}
/**
* #param \Dagou\Resume\Domain\Model\Resume $resume
*/
protected function saveAction(\Dagou\Resume\Domain\Model\Resume $resume) {
$this->resumeRepository->add($resume);
}
}
Template
<f:form class="form-horizontal" name="resume" action="save" object="{resume}">
<f:if condition="{resume.builders}">
<f:for each="{resume.builders}" as="builder" iteration="builderIteration">
<f:form.textfield class="form-control" property="builders.{builderIteration.index}.header" />
</f:for>
</f:if>
</f:form>
If you have a better one, please let me know. Thanks!

Difference between modelAttribute and commandName attributes in form tag in spring?

In Spring 3, I have seen two different attribute in form tag in jsp
<form:form method="post" modelAttribute="login">
in this the attribute modelAttribute is the name of the form object whose properties are used to populate the form. And I used it in posting a form and in controller I have used #ModelAttribute to capture value, calling validator, applying business logic. Everything is fine here. Now
<form:form method="post" commandName="login">
What is expected by this attribute, is it also a form object whose properties we are going to populate?
If you look at the source code of FormTag (4.3.x) which backs your <form> element, you'll notice this
/**
* Set the name of the form attribute in the model.
* <p>May be a runtime expression.
*/
public void setModelAttribute(String modelAttribute) {
this.modelAttribute = modelAttribute;
}
/**
* Get the name of the form attribute in the model.
*/
protected String getModelAttribute() {
return this.modelAttribute;
}
/**
* Set the name of the form attribute in the model.
* <p>May be a runtime expression.
* #see #setModelAttribute
*/
public void setCommandName(String commandName) {
this.modelAttribute = commandName;
}
/**
* Get the name of the form attribute in the model.
* #see #getModelAttribute
*/
protected String getCommandName() {
return this.modelAttribute;
}
They are both referring to the same field, thus having same effect.
But, as the field name indicates, modelAttribute should be preferred, as others have also pointed out.
OLD WAY = commandName
...
<spring:url value="/manage/add.do" var="action" />
<form:form action="${action}" commandName="employee">
<div>
<table>
....
NEW WAY = modelAttribute
..
<spring:url value="/manage/add.do" var="action" />
<form:form action="${action}" modelAttribute="employee">
<div>
<table>
..
I had the same question a while ago, I can't remember the exact differences but from research I ascertained that commandName was the old way of doing it and in new applications you should be using modelAttribute
commandName = name of a variable in the request scope or session scope that contains the information about this form,or this is model for this view. Tt should be a been.
In xml based config, we will use command class to pass an object between controller and views. Now in annotation we are using modelattribute.

An error occurred while trying to call Controller->createAction()

I am trying to create something with extbase, but the error-message I get is not very helpful. I took the blog_example extension as a guide. A (maybe) important difference is: I don't have a database table because I want to write a custom domain repository that connects to an external servive through REST.
The actual error message (displayed above the plugin, not as an exception message):
An error occurred while trying to call Tx_MyExt_Controller_SubscriptionController->createAction()
Classes/Controller/SubscriptionController:
Stripped down to the important parts.
class Tx_MyExt_Controller_SubscriptionController extends Tx_Extbase_MVC_Controller_ActionController
{
/**
* #var Tx_MyExt_Domain_Repository_SubscriberRepository
*/
protected $subscriberRepository;
/**
* #return void
*/
public function initializeAction()
{
$this->subscriberRepository = t3lib_div::makeInstance('Tx_MyExt_Domain_Repository_SubscriberRepository');
}
/**
* #param Tx_MyExt_Domain_Model_Subscriber $subscriber
* #dontvalidate $subscriber
* #return string The rendered view
*/
public function newAction(Tx_MyExt_Domain_Model_Subscriber $subscriber = null)
{
$this->view->assign('subscriber', $subscriber);
}
/**
* #param Tx_MyExt_Domain_Model_Subscriber $subscriber
* #return string The rendered view
*/
public function createAction(Tx_MyExt_Domain_Model_Subscriber $subscriber)
{ }
}
Classes/Domain/Model/Subscriber
class Tx_MyExt_Domain_Model_Subscriber extends Tx_Extbase_DomainObject_AbstractEntity
{
/**
* #var string
* #dontvalidate
*/
protected $email = '';
/**
* #param string $email
* #return void
*/
public function setEmail($email)
{
$this->email = $email;
}
/**
* #return string
*/
public function getEmail()
{
return $this->email;
}
}
Resources/Private/Templates/Subscription/new
<f:form action="create" controller="Subscription" objectName="Subscriber" object="{subscriber}" method="post">
<f:form.textfield property="email"></f:form.textfield>
<f:form.submit value="submit"></f:form.submit>
</f:form>
Facts
Adding $subscriber = null removes the message. But $subscriber is null then
A var_dump($this->request->getArguments()); displays the form's fields
There is an index action, and it is also the first action defined in ext_localconf.php
The hints and solutions I found aren't working for me, so I hope someone can guide me into the right direction.
I've got the same bug.
If you pass an Model as argument to an method, it will also validate the model fields.
I've had this annotation on my model property:
/**
*
* #var \string
* #validate NotEmpty
*/
It validates the "#validate" annotation.
The field in the database was empty so i got the error message
An error occurred while trying to call ...
It would be good if there was a better error message.
You need to customize the validation annotation or verify that the property is not empty in the database
Hope it helps somebody
In addtion: check any Validations in your Model and your TCA. If a field is marked as #validate NotEmpty in your Model and is not marked appropriately in the TCA, a record can be saved ignoring the #validate settings in the Model. This can happen if you change the Model and/or TCA after creating records.
An example:
Field 'textfield' is set to not validate, both in the TCA and the Model. You create a new record and save it without filling in the field 'textfield' (you can, it is not set to validate). You then change the Model setting 'textfield' to #validate NotEmpty and then try to show the record on the FE, you will get the error.
The solution for that example:
Simply remove the validation in your Model OR check validations in the TCA and Model so that they work together.
--
A German blog post covers this solution: http://www.constantinmedia.com/2014/04/typo3-extbase-an-error-occurred-while-trying-to-call-anyaction/
just override the template method getErrorFlashMessage in yout controller to provide a custom error message...
/**
* A template method for displaying custom error flash messages, or to
* display no flash message at all on errors. Override this to customize
* the flash message in your action controller.
*
* #return string|boolean The flash message or FALSE if no flash message should be set
* #api
*/
protected function getErrorFlashMessage() {
return 'An error occurred while trying to call ' . get_class($this) . '->' . $this->actionMethodName . '()';
}
classic case of "start over from scratch and it works, and if you compare it you have the same code, though".
I updated the code in the question, maybe it helps someone.