Silverstripe 3 GridField with many_many relationship

After getting used to the DataObjectManager in Silverstripe 2.4 and below I’ve been trying to achieve similar functionality using the new GridFields in Silverstripe 3. A fairly common situation I encounter is the need to handle many to many relationships. In the below example, I have Tour Pages, which can have many Activities associated with them and an Activity can also be associated with many Tour Pages.

We create the activity class:

MySite/Code/Activity.php

class Activity extends DataObject{
	public static $db = array(
		'Title' => 'Varchar(255)',
  		'Summary' => 'HTMLText'
 	);
 
 	static $belongs_many_many = array( 
		'TourPages' => 'TourPage' 
	);
 
 	public static $summary_fields = array(
  		'Title' => 'Title'
 	);
 
 	public function getCMSFields_forPopup() {
	  	return new FieldList(
	   		new TextField('Title', 'Title'),
	   		new HTMLEditorField('Summary', 'Brief Summary'),
	  	);
	}
}

We now need to add the relationship to the tour page and create the GridField: Note the unlinkrelation delete action. There was a change to the codebase in the last 7 days or so (at date of post) which changed the action of the delete button in GridFields to actually delete the item rather than unlink the relation. This adds in the unlink relation button so it will not affect any of your other relations.

MySite/Code/TourPage.php

static $many_many = array(
	'Activities' => 'Activity'
);
 
function getCMSFields() {
	$fields = parent::getCMSFields();
 
	...
	$gridFieldConfig = GridFieldConfig_RelationEditor::create()->addComponents(
		new GridFieldDeleteAction('unlinkrelation')
	);
 
	$gridField = new GridField("Activities", "Activities", $this->Activities(), $gridFieldConfig);
 
	$fields->addFieldToTab("Root.Activities", $gridField);
 
	return $fields;
}

Once you have run a dev/build you will have something similar to the below.

You may have noticed the “Allow Drag and Drop” link to allow you to order these.

To allow Drag and Drop reordering, you will need to download the SortableGridField module and run a dev/build/flush=all. You will also need to add a few additional lines of code to the Tour Page.

Firstly you need to add a many_many_extraFields static to add an extra row to the linking table to store the sort order. In my example:

static $many_many_extraFields = array( 
	'Activities' => array( 
		'SortOrder' => "Int" 
	)
}

Finally add the GridFieldSortableRows to your GridField config:

$gridFieldConfig = GridFieldConfig_RelationEditor::create()->addComponents(
	new GridFieldDeleteAction('unlinkrelation'),
	new GridFieldSortableRows('SortOrder') //add to allow sorting of many_many's
);

You will also need to sort the GridField based on the sort order field created above when you are creating it:

$gridField = new GridField("Activities", "Activities", $this->Activities()->sort('SortOrder'), $gridFieldConfig);

So the final code looks like this:

MySite/Code/Activity.php

class Activity extends DataObject{
	public static $db = array(
		'Title' => 'Varchar(255)',
  		'Summary' => 'HTMLText'
 	);
 
 	static $belongs_many_many = array( 
		'TourPages' => 'TourPage' 
	);
 
 	public static $summary_fields = array(
  		'Title' => 'Title'
 	);
 
 
 	public function getCMSFields_forPopup() {
	  	return new FieldList(
	   		new TextField('Title', 'Title'),
	   		new HTMLEditorField('Summary', 'Brief Summary'),
	  	);
	}
}

MySite/Code/TourPage.php

static $many_many = array(
	'Activities' => 'Activity'
);
static $many_many_extraFields = array( 
        'Activities' => array( 
                'SortOrder' => "Int" 
        )
);
 
function getCMSFields() {
	$fields = parent::getCMSFields();
 
	...
	$gridFieldConfig = GridFieldConfig_RelationEditor::create()->addComponents(
		new GridFieldDeleteAction('unlinkrelation'),
                new GridFieldSortableRows('SortOrder') //add to allow sorting of many_many's
	);
 
	$gridField = new GridField("Activities", "Activities", $this->Activities()->sort('SortOrder'), $gridFieldConfig);
 
	$fields->addFieldToTab("Root.Activities", $gridField);
 
	return $fields;
}
Did you like this post? Why not subscribe?

9 thoughts on “Silverstripe 3 GridField with many_many relationship

  1. Have you noticed how you have to first create the DataObject or Page before an upload field will set the appropriate relationship? If you upload an image before pressing create (on a has_one or a has_many) the image will not be associated with the DO or Page and will just disappear into the Files Nirvana. Just making sure it’s not just me and I;m not doing something wrong!

  2. Hello Fraser,

    thank you for the excellent description. I’m now able to sort items on a related dataobject.

    There’s one problem, I do not know why it appears. If I update an item (Activity in your case) the SortOrder is set to the highest amount of linked items. For example if there are 5 activities linked to a tour and the activity is on first position, it will change to fitfth position after writing.

    It would be great if you can give me some hint.

    Best Markus

  3. Do you know how to sort in the front end. For example to allow the user to sort them alphabetically and also separately by category (say there are 5 groups A,B,C,D,E) So that all group 5 are together etc, or all groups are mixed but in Alphabetical order according to another part of there data (their name for example).
    In addition to this do you know if in the front end you can use the search to specifically search for (in your case) an activity. And just so the results from this gridfield. Thanks

  4. Pingback: CopyQuery | Question & Answer Tool for your Technical Queries

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>