Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

We've moved the forum!

Please use forum.silverstripe.org for any new questions (announcement).
The forum archive will stick around, but will be read only.

You can also use our Slack channel or StackOverflow to ask for help.
Check out our community overview for more options to contribute.

Data Model Questions /

Moderators: martimiz, Sean, Ed, biapar, Willr, Ingo, swaiba

Pagination of a Has_Many element


Go to End


3 Posts   2139 Views

Avatar
DesignCollective

Community Member, 66 Posts

16 June 2010 at 8:40pm

I have been searching all references and been trying the DataObjectModel examples for pagination until I realized that a has_many reference isn't part of the DOM scope. Duh.

Sorry to be "lazy", I've spent about 3 days on this now, loved the challenge of trying to crack it, but now I am just tired. Here's what I am trying to do.

class ProfileListPage extends Page {
	
   	static $db = array(
   	);
	static $has_one = array(
	);
	static $has_many = array(
		'Performers' => 'ProfileListProfile'
	);
	
	public function getCMSFields()
	{
		$f = parent::getCMSFields();

		$performerManager = new DataObjectManager(
			$this, // Controller
			'Performers', // Source name
			'ProfileListProfile', // Source class
			array(
				'Name' => 'Name', 
				'LastName' => 'LastName', 
				'Location' => 'Location',
				'Sex' => 'Sex', 
				'Styles' => 'Styles', 
				'CV' => 'CV',
				'About' => 'About',
				'Thumbnail' => 'Image',
				'Links' => 'ProfileListLink'
			),
			'getCMSFields_forPopup',
			 '',
			 'LastName ASC'
		);

		$f->addFieldToTab("Root.Content.Profiles",$performerManager);		
		
	}
}

The underlying ProfileListProfile class is here:

class ProfileListProfile extends DataObject
{
	static $db = array (
		'Name' => 'Text',
		'LastName' => 'Text',
		'Location' => 'Text',
		'Sex' => "Enum('Male, Female', 'Female')",
		'Styles' => 'Text',
		'CV' => 'Text',
		'About' => 'Text'
	);
	
	static $has_one = array (
		'Image' => 'Image',
		'Page' => 'ProfileListPage'
	);
	
	static $has_many = array (
		'Links' => 'ProfileListLink'
	);
	
	function Thumbnail() {
		$Image = $this->Image();
		if ( $Image ) {
			return $Image->CMSThumbnail();
		} else {
			return null;
		}
	}
	
	static $default_sort = "LastName ASC"; 
		
	public function getCMSFields_forPopup()
	{
		return new FieldSet(
			new TextField('Name'),
			new TextField('LastName'),
			new DropdownField('Sex', 'Choose', singleton('ProfileListProfile')->dbObject('Sex')->enumValues()),
			new TextField('Location'),
			new TextAreaField('Styles'),
			new TextAreaField('CV'),
			new SimpleImageField('Image','Profile Picture',null,null,null,'ProfilePhotos'),
			new DataObjectManager ($this, 'Links', 'ProfileListLink', array('Title'=>'Link Name', 'Link' => 'URL'))
		);
	}
}

Now... On a given ProfileListPage there should display a limited number (8) subset of Performers. The template sees $this->Performers properly, but I just need a subset. I am not good enough with the Silverstripe database queries and dataset casting to figure out how on a given page I can have e.g. a CurrentSet which will include the records from 0-7 if no argument is passed to the page. If a page argument is somehow passed, the next 8 - and so on - will display. I wasn't even sure whether I'd need to create a special action on the page (how would a CurrentSet function otherwise receive the page argument?). Sorry for requesting to be babied, but I'm really stuck here :) Thanks.

Avatar
MarcusDalgren

Community Member, 288 Posts

16 June 2010 at 9:06pm

One babying coming up!

I'm assuming that you want to paginate when displaying on the frontend through one of your templates.
What you have to do is write a custom getter in your ProfileListPage that takes pagination into account. There's an article about pagination here but I'm going to walk you through it for this specific case.

So let's call your function getPaginatedPerformers (a rip from the recipe but modified slightly for your case)

function getPaginatedPerformers() {
  if(!isset($_GET['start']) || !is_numeric($_GET['start']) || (int)$_GET['start'] < 1) $_GET['start'] = 0;
  $SQL_start = (int)$_GET['start'];
  $doSet = DataObject::get(
    $callerClass = "ProfileListProfile",
    $filter = "`PageID` = '".$this->ID."'",
    $sort = "",
    $join = "",
    $limit = "{$SQL_start},8"
  );
 
  return $doSet ? $doSet : false;
}

Now you can call this function in your templates through <% control PaginatedPerformers %> note that we leave out "get" when calling model functions in the template. Put anything you want within the control and then after that you add your pagination controls. They can look like this for example (ripped from the recipe but with your variable names)
<% if PaginatedPerformers.MoreThanOnePage %>
  <p>
  <% if PaginatedPerformers.PrevLink %>
    <a href="$PaginatedPerformers.PrevLink">&lt;&lt; Prev</a> | 
  <% end_if %>
 
  <% control PaginatedPerformers.Pages %>
    <% if CurrentBool %>
      <strong>$PageNum</strong> 
    <% else %>
      <a href="$Link" title="Go to page $PageNum">$PageNum</a> 
    <% end_if %>
  <% end_control %>
 
  <% if PaginatedPerformers.NextLink %>
    | <a href="$PaginatedPerformers.NextLink">Next &gt;&gt;</a>
  <% end_if %>
  </p>
<% end_if %>

Try this out and let me know if it works!

Avatar
DesignCollective

Community Member, 66 Posts

20 June 2010 at 7:26am

Edited: 20/06/2010 7:26am

Amazing, that worked like a charm. I didn't even realize you could just pass the page variable via url, thought I'd have to rely on actions and ID's. That "get-" prefix for e.g. getPaginatedPerformers vs PaginatedPerformers in the template, I never knew that was possible / necessary. Thank you so much!