$many_many relation in front end form


#1

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?


#2

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.


#3

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>

#4

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


#5

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.


#6

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.