$many_many relation in front end form

Silverstripe Version: 4.1

Not sure how to go about representing this relationship in a front end form? I couldn’t see any repeatable type fields or examples.

Relationship in Event data object:

$many_many = [
    EventLinks => EventLink::class
]; 

EventLink object has the opposite with belongs_many_many.

EventLink is just a Title and a Url of a link.

I started off thinking I would make a CompositeField in the Form for the EventLink object, like:

CompositeField::create(
	TextField::create('WebsiteName')->setName('Links[WebsiteName]'),
	TextField::create('WebsiteUrl')->setName('Links[WebsiteUrl]')
)->setName('Links'),

Used in a form template:

<div id="LinkWrapper">
    $Fields.FieldByName('Links')
</div>
<button id="AddMoreLinks" type="button"">Add more links</button>

And write some JS to clone and copy off an add button.

How are the values populating the form going to work though? In the case of an edit with 5 existing EventLink records?

Is there a better/different way to go about it?

As Event links is a many many relation you need a loop to display them on a template.

<div id="LinkWrapper">
<% if $EventLinks %>
     <% loop $EventLinks %>
            $ME
    <% end_loop %>
<% end_if %>
</div>

You can replace $ME with your DBFields from EventLink.

Thanks @Greg_808 the example there was just for the template.

So far I’m starting off with a base of:


# In form building function
$LinksCompField = CompositeField::create();

$LinksTemplateField = CompositeField::create(
	TextField::create('EventLinksTemplate[Title]', 'Website label'),
	TextField::create('EventLinksTemplate[Url]', 'Website Url')
)->setName('EventLinksTemplate');

if ($this->ExistingID)
{
	$Event = Event::get_by_id('Event', $this->ExistingID);

	$Links = $Event->EventLinks();

	if ( ! empty($Links))
	{
		$LinksCompField = CompositeField::create();

		foreach ($Links as $Link)
		{
			$LinksCompField->push(TextField::create('EventLinks['.$Link->ID.'][Title]', 'Website label')->setValue($Link->Title));
			$LinksCompField->push(TextField::create('EventLinks['.$Link->ID.'][Url]', 'Website Url')->setValue($Link->Url));
		}
	}
}
else if ( ! empty($_POST) and array_key_exists('EventLinks', $_POST))
{
	foreach ($_POST['EventLinks'] as $key => $Link)
	{
		$LinksCompField->push(TextField::create('EventLinks['.$key.'][Title]', 'Website label')->setValue($Link['Title']));
		$LinksCompField->push(TextField::create('EventLinks['.$key.'][Url]', 'Website Url')->setValue($Link['Url']));
	}
}

$LinksCompField->setName('Links');

Template:

<div id="LinkWrapper">
    $Fields.FieldByName('Links')
</div>
<div id="LinkTemplate">
    <!— Template below will be used for clone —>
    $Fields.FieldByName('EventLinksTemplate')
</div>
<button id="AddMoreLinks" type="button">Add more links</button>

I have no idea what you are trying to achieve. You want to upload links from the front end with a form?

g

UI above. I have an Event DataObject with a many_many EventLinks DO. Event links is simply 2 text fields as shown above.

Upon create, a user should see an empty EventLink set. They can fill it out and click “Add more links” to get a new blank set.

Editing they should see all the Links they have made.

That’s all.

This is looking impossible, or at least difficult… Seems SS doesn’t allow dynamically added form fields (what the clones would be above) due to this line in FormRequestHandler

$allowedFields = array_keys($this->form->Fields()->saveableFields());

Cloned fields don’t appear in that list. Form->loadDataFrom also uses that list.

if there’s a field validation error, it never gets to the doEventsForm (so I could store $_POST) and of course the EventForm has no $_POST as it was redirected back.

So far resorted to extending FormRequestHandler and adding a session store of $_POST to the httpSubmission method for use in EventForm.

Nobody in the history of SS has ever needed dynamic fields on a front end form before?!

I’m all ears for a better approach.