I have following relations in my model: Request 1:n Hall (one-to-many)
In Request model class I have
/**
* hall
*
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Vendor\Ext\Domain\Model\Hall>
* #cascade remove
*/
protected $hall = null;
In Hall model class I have
/**
* num
*
* #var string
* #validate NotEmpty
*/
protected $num = '';
Can I set multiple items in one Fluid form? Like
<f:form avction="create" name="hall" object="{hall}" controller="Hall">
<f:form.textfield name="hall[num][]" class="form-control" />
<f:form.textfield name="hall[num][]" class="form-control" />
<f:form.submit value="Create" />
</f:form>
You are close to the solution, the field name you wrote is missing the proper plugin namespace however. There is an easier solution for this:
<f:form action="create" name="request" object="{request}" controller="Request">
<f:form.textfield property="hall.0.num" class="form-control"/>
<f:form.textfield property="hall.1.num" class="form-control"/>
<f:form.submit value="Create"/>
</f:form>
It is essential that you create your root entity (request here) with the form and all relations through appropriate form fields. Using property ensures the proper name (including plugin namespace) for all fields, in this case e.g. name="tx_myext_myplugin[request][hall][0][num]".
As you probably noticed you can add as many relation objects as desired as long as you use a numeric index for each object. For many fields you could use the f:for viewhelper.
I personally struggle with the automapping of typo3 so I would do the following:
add the following funcs to your model
public function addHall(Hall $hall){
$this->hall->atach($hall)
}
pubflic function removeHall(Hall $hall){
$this->hall->detach($hall)
}
Note: Kepp in mind that you have to declare $hall al object storage
Now you can create a new hall object in your controller, persist the new hall, add it with the addHall-method to yor desired model and persist.
Hint: there are nice little functions fpr persisting; similar to PersistanceManager::persistAll() or s.th. like that
Related
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.
I have an ordninary model called "Mail":
Namespace ...
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Mail extends AbstractEntity
{
/**
* #var string
*/
protected $name;
protected $company;
.../**
* #var string
*/
protected $company;
...
I want to use it in a Form:
<f:form action="post" object="{mail}">
<f:form.textfield property="name"/>
...
</f:form>
First weird thing is, the html the viewhelper generates is:
<input name="tx_myext_offer[name]">
But in order to work it should be:
<input name="tx_myext_offer[mail][name]">
So I try to write the html of the input field manualy with the name attribute like "tx_myext_offer[mail][name]".
When I now send the form to the controller I get an error:
#1297759968: Exception while property mapping at property path "": It is not allowed to map property "name". You need to use $propertyMappingConfiguration->allowProperties('name') to enable mapping of this property.
When I debug the PropertyMappingConfiguration Object of the request I see that the "propertiesNotToBeMapped" Attribute is empty. There should be the attributes of the Mail model.
Somehow extbase does not map it automatically this time. Seems like I missed something somewhere. How can I tell extbase to map the the properties of the Model automatically?
#ThomasLöffler
in the controller Action which calls the form there is nothing exciting happening:
public function showAction()
{
$this->view->assignMultiple(
[
'mail' => $this->objectManager->get(Mail::class)
]
);
}
First thing first. You have missed the attribute objectName="mail" in your <f:form /> tag.
When you add this attribute the hidden field tx_myext_offer[__trustedProperties] and a bunch of others will changes and then your automatic property mapping should work.
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?
update 31.01.
Meanwhile I made a new test Ext as written on Marcels webpage:
http://lbrmedia.net/codebase/Eintrag/extbase-bidirektionale-mm-relation/
I have the same Issue with that.
The goal ist that a Feuser (logged-in) can see all available clients and pick his favorites. So every Feuser can have many clients and each client can be picked by many Feusers. The table relations are correct. In the backend I can see and update the relations on every side (clients can pick Feusers from the list and vice versa.
Please find all the data needed in my Gist:
https://gist.github.com/metaxos/91622c536588d0aa8440
The ZIP of the extension can be found here: http://www.filedropper.com/testmm000201501311251
initial question
For a small Extbase extension. I have a m:n connection between Feusers and Clients. Each Feuser can have multiple clients associated. I have build the relation with Extension Builder and in the backend everything works as needed.
In my frontend plugin I can also add clients to my feuser with attach (this works).
Every Feuser can see all clients (this works). In the for loop of all clients I want to show an icon if the client is associated with the feuser (this works).
But how can i avoid the inner for loop (see below)?
<f:for each="{feusers.clients}" as="singleClient">
Feusers model:
/**
* Clients
*
* #lazy
* #var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<Exapoint\Exaibbrplus\Domain\Model\Clients>
*/
protected $clients;
/**
* __construct
*/
public function __construct() {
$this->initStorageObjects();
}
/**
* Initializes all ObjectStorage properties.
*
* #return void
*/
protected function initStorageObjects() {
$this->clients = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
}
/**
* Returns the clients
* #return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\Exapoint\Exaibbrplus\Domain\Model\Clients> $clients
*/
public function getClients() {
return $this->clients;
}
List.html:
<f:for each="{clients}" as="client">
<tr>
<td>
<f:for each="{feusers.clients}" as="singleClient">
<f:if condition="{client.identifier}=={singleClient.identifier}">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</f:if>
</f:for>
</td>
</tr>
</f:for>
Relation:
Here's the dump of my clients of feusers:
In my opinion, if a FrontendUser can have n clients, but a client can have only one FrontendUser, you should have used an 1:n relation.
Somehow, your code is very distracting: You iterate through {feusers.clients}, but you can only do that if feusers is in fact not an ObjectStorage of FrontendUsers, but a single FrontendUser. Maybe you could post more of your setup.
A suggest you add a transient property to your Clients model, e.g.
/**
* #var boolean
* #transient
*/
protected $isChildOfAuthenticatedUser`
For this property you create a getter. The getter compares if the currently authenticated user is identical to the parent user of the client. In the example I'm assuming that a Client only has one parent/FrontendUser:
/**
* #return boolean
*/
public function $isChildOfAuthenticatedUser {
if ((int)$GLOBALS['TSFE']->fe_user->user['uid'] === $this->frontendUser->getUid()) {
return TRUE;
} else {
return FALSE;
}
}
Then, in your Fluid template, you just need to check on that transient property:
<f:if condition="{client.isChildOfAuthenticatedUser}">
<span class="glyphicon glyphicon-star" aria-hidden="true"></span>
</f:if>
Keep in mind to add some additional check if the data is also to be displayed if no user is authenticated.
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.