I've got three DataObjects in Silverstripe 3.1: an Issue, a Vote, and a Voter. Issues have many Votes; Votes have one Voter and one Issue. On my Issue_show page, I want to show all that Issue's Votes, sorted by Voter's Name.
The function in the Issue looks like this:
public function MyVotes() {
return $this->Votes();
}
But I can't figure out how to access the Voter's Name to sort by it. Presumably, it should be something like
public function MyVotes() {
return $this->Votes()->sort('Voter.Name');
}
but that throws an error. What step am I missing?
For a has_one relation you need to add the ID suffix to the fieldname. Also, relation casting in DataList->sort() unfortunately does only work with an array.
public function MyVotes() {
return $this->Votes()->sort(array('VoterID.Name'=>'ASC'));
}
You could also handle sorting in the template something like this:
<% loop Votes.Sort('VoterID.Name') %>
...
This hasn't been tested but pretty sure that should work
Related
Using SonataAdmin 4.x here.
As the title says, I'm trying to get the subject in configureRoutes() function.
Based on a property of the subject I'm editing, I'd like to remove a route as you can see in the code snippet down here:
public function configureRoutes(RouteCollectionInterface $collection): void {
if ($this->hasSubject() && $this->getSubject()->isProtected()) {
$collection->remove('delete');
}
}
It seems to me, that the subject somehow gets cached and is not up to date or not existent at all.
Am I missing something here or is there a better solution to this?
I've got an issue with my fieldset in Zend Framework 2.
The user is able to save his personal data over a form. If he already save this, they should be prefilled with the data from database. This worked fine as it was only a form, but I need the address data in a fieldset, so that I can use it at other parts of my program. Now the input fields stays empty.
At the beginning, I fill the personal data in a session. My data looks like this:
object(Application\Model\Product\PersonalData)#247 (3) {
["tel":protected]=> string(0) ""
["birthday":protected]=> string(10) "2013-01-01"
["address":protected]=> object(Application\Model\Account\Addresses)#248 (15) {
["firstname":protected]=> string(5) "Ernie"
["surname":protected]=> string(6) "Muppet"
...
}
}
As you can see, the data is already bind to the given objects, PersonalData as main, and Addresses for the fieldset. This seems to work then.
Then I put it in my form:
$oForm->setData($oForm->getHydrator()->extract($_SESSION->getPersonalData()));
return new ViewModel(array('form'=>$oForm));
The addressFieldset has a hydrator and a binding, which does work, because all objects are perfectly filled. The only problem is, that when I open the page, the input-fields are empty, only birthday and telephone are filled, which are directly on the form
My form implements the address-fieldset like this:
$addressFieldset = new AddressFieldset($lang);
$addressFieldset->setUseAsBaseFieldset(true);
$addressFieldset->setName('address');
$this->add($addressFieldset);
I think that it might be just a problem with the correct addressing of my fieldset, but I can't explain why it would be filled correctly after posting the data then. All I want is that he fill the setData in my Fieldset.
I hope you understand my question and could help me.
Thanks a lot,
Svenja
EDIT:
I analysed it a bit more now, it's very strange and I don't know what went wrong.
When I debug I can see that $fieldset->getValue() returns all data I need. So I thought that the binding might be wrong and I did this to debug it step by step:
$values = $this->form->get('address')->getValue();
$addressFieldset = $this->form->get('address');
$aValues = $addressFieldset->getHydrator()->extract($values);
$addressFieldset->bindValues($aValues);
I went to the Fieldset.php and bindValues does perfectly what it should (it is only a recapitulation anyway(?)), call the hydrator and fill my object. But if I show in the elements, all values are NULL.
I already checked my elements twice. The only different between the model and the elements is a different order of declaration. I call the method setUseAsBaseField(true) in the fieldset and the form, too. I can't understand why the data is in values but not in elements.
It's very strange, because I have something and it good work. Are you confident that the expression $_SESSION->getPersonalData() to return the desired result? You are using a very strange session.
http://framework.zend.com/manual/2.1/en/modules/zend.session.container.html
To EDIT (I'm sorry my english):
You can use different types of hydrator, for example
ArraySerializable (by default, your entity must have getArrayCopy()
and exchangeArray() methods, in your case) ArraySerializable is the hydrator by default.
First you have to bind a form with entity $form->bind(new Entity()); The entity will bind to the base fieldset. If the base fieldset not specified, the entity will bind with the form, because the form inherits fieldset. If the entity is an aggregate, ie, its properties contain another objects, for each of these objects should be your fildset.
In constructor this children fieldsets you should use
$this->setObject(new MyChildrenEntity());
As a result, the entity properties will be extracted to the form
elements.
After that, you should only work with the form, but not with its
elements or fieldsets.
You can pass any data in the form, so form elements will get this
values. $form->setData($this->getRequest()->getPost()); This method use internal populateValues() method. If the form has elements with an appropriate name, they will be assigned to these values. If the form has fieldsets, they will also be transferred to these values.
As long as the form fails to validate, the entity of these values
will not be assigned. These values are assigned to entities only
in case of successful verification. IsValid () method uses the
internal method bindValues () if the validation was successful.
After successful validation, you can get the entity using getData()
method $entity = $form->getData();
P.S.: If you are doing a complex "haсk", do not be offended by this simple explanation.
I finally found the solution!
At first, this link helped me:
Populating fieldsets
I didn't integrate the fieldset Input-Filter in the Form Input Filter.
But that wasn't all I have to do. My Form-Datamodel looks like this:
interface IPersonalData
{
public function getTel();
public function getBirthday();
public function getAddress();
public function setTel($tel);
public function setAddress(IAddresses $address);
public function setBirthday($birthday);
}
This is only the interface, but Address is an object. And that is the problem. When the form tries to fill the fieldset, he only accept arrays. So I have to extract my object in the getter-method to an array.
I don't think that this is very useful, because I normaly want to get my object with this method. So I write a method "getAddressAsArray()" which looks like this:
public function getAddressAsArray()
{
$oAddressHydrator = new AddressHydrator();
if(isset($this->address))
{
return $oAddressHydrator->extract($this->address);
}
return array();
}
The extract-method of my hydrator changed like this:
public function extract($object)
{
if(!$object instanceof IPersonalData)
{
throw new \InvalidArgumentException('$object must be an instance of Application\Model\Product\IPersonalData');
}
return array(
'telephone' => $object->getTel(),
'address' => $object->getAddressAsArray(),
'birthday' => $object->getBirthday(),
);
}
I need to add an additional field to InventJournalTrans, that after posting will show up in the InventTrans table. The field is a reference column to a record in another table. What method(s) do I need to modify to make this behavior happen?
Currently, I have already added the fields to both tables and modified the Form to allow the user to enter and save the new field. I just can't seem to find the bottom of the rabbit hole on where the actual posting to InventTrans is occurring.
Ideally, it should just be a:
inventTrans.ReasonRefRecId = inventJournalTrans.ReasonRefRecId;
assignment statement before the
inventTrans.insert();
call. Anybody have a clue on where this is at?
The link above does contain the solution -- I have included the code from that page in case that page disappears or no longer becomes available. Thanks to gl00mie for answering on that site and providing this answer.
You should create a new InventMovement method like this:
public MyNewFieldType myNewField()
{
return MyNewFieldType::DefaultValue; // suppose your new field is an enum
}
Then modify \Classes\InventMovement\initInventTransFromBuffer
void initInventTransFromBuffer(InventTrans _inventTrans, InventMovement _movement_orig)
{
// ... append this line to the end of whatever else is already in this method
_inventTrans.MyNewField = this.myNewField();
}
And finally overload the new method in the InventMov_Journal class:
public MyNewFieldType myNewField()
{
return inventJournalTrans.MyNewField;
}
I have just realized that, by chance, I implemented a category browse type situation in my project:
// GET: /Category/Browse?Category=Fruit
public ActionResult Browse(string category)
{
...
}
It turns out this is special case and there must be something behind the scenes. My next one I want to implement something like
//GET: /Category/Browse?Color=Blue
public ActionResult Browse(string color)
{
...
}
You get the idea...
Where/how do I register the different url values?
You don't need to register anything. Action parameters are automatically mapped to URL values by the default model binder. You can also map to complex type, list and dictionary parameters.
Using FluentMigrator, the default creation of a Column using .AsString() results in an nvarchar(255). Is there a simple way (before I modify the FluentMigrator code) to create a column of type nvarchar(MAX)?
You could create an extension method to wrap .AsString(Int32.MaxValue) within .AsMaxString()
e.g.
internal static class MigratorExtensions
{
public static ICreateTableColumnOptionOrWithColumnSyntax AsMaxString(this ICreateTableColumnAsTypeSyntax createTableColumnAsTypeSyntax)
{
return createTableColumnAsTypeSyntax.AsString(int.MaxValue);
}
}
OK, I found it. Basically, use .AsString(Int32.MaxValue). Pity there's not a .AsMaxString() method, but I guess it's easy enough to put in...
You can use AsCustom("nvarchar(max)") and pack it to extension
If you often create columns/tables with the same settings or groups of columns, you should be creating extension methods for your migrations!
For example, nearly every one of my tables has CreatedAt and UpdatedAt DateTime columns, so I whipped up a little extension method so I can say:
Create.Table("Foos").
WithColumn("a").
WithTimestamps();
I think I created the Extension method properly ... I know it works, but FluentMigrator has a LOT of interfaces ... here it is:
public static class MigrationExtensions {
public static ICreateTableWithColumnSyntax WithTimestamps(this ICreateTableWithColumnSyntax root) {
return root.
WithColumn("CreatedAt").AsDateTime().NotNullable().
WithColumn("UpdatedAt").AsDateTime().NotNullable();
}
}
Similarly, nearly every one of my tables has an int primary key called 'Id', so I think I'm going to add Table.CreateWithId("Foos") to always add that Id for me. Not sure ... I actually just started using FluentMigrator today, but you should always be refactoring when possible!
NOTE: If you do make helper/extension methods for your migrations, you should never ever ever change what those methods do. If you do, someone could try running your migrations and things could explode because the helper methods you used to create Migration #1 works differently now than they did earlier.
Here is the code for creating columns incase it helps you create helper methods: https://github.com/schambers/fluentmigrator/blob/master/src/FluentMigrator/Builders/Create/Column/CreateColumnExpressionBuilder.cs
How about extending like this:
public static class StringMaxMigratorExtensions
{
public static ICreateTableColumnOptionOrWithColumnSyntax AsStringMax(this ICreateTableColumnAsTypeSyntax createTableColumnAsTypeSyntax)
{
return createTableColumnAsTypeSyntax.AsCustom("nvarchar(max)");
}
public static IAlterColumnOptionSyntax AsStringMax(this IAlterColumnAsTypeSyntax alterColumnAsTypeSyntax)
{
return alterColumnAsTypeSyntax.AsCustom("nvarchar(max)");
}
}