SSbits - Home page
Site by Carbon Crayon
Submit a Post >

Tutorials - Big bits of code to help you do more

Custom sorting in the CMS SiteTree

SSBtreesorting

Ever wanted to sort some parts of the SiteTree? Well now you can!

Hierarchy explained

There is a class in Sapphire called Hierarchy (sapphire/core/model/Hierarchy.php) which adds the capability to put DataObjects in a parent-child relationship. Hierarchy also comes with alot of methods for fetching lists of children either as DataObjectSets or as UL-lists. The method Children for example, which you might have used to list sub pages can be found in Hierarchy on line 393 or thereabouts.

However the method we're interested in today is the method called doAllChildrenIncludingDeleted. This method is called by AllChildrenIncludingDeleted and is the method that's being used to populate the SiteTree in the CMS. The method looks like this

 

public function doAllChildrenIncludingDeleted($context = null) {
	if(!$this->owner) user_error('Hierarchy::doAllChildrenIncludingDeleted() called without $this->owner');
	
	$idxStageChildren = array();
	$idxLiveChildren = array();
	
	$baseClass = ClassInfo::baseDataClass($this->owner->class);
	if($baseClass) {
		$stageChildren = $this->owner->stageChildren(true);
		
		// Add live site content that doesn't exist on the stage site, if required.
		if($this->owner->hasExtension('Versioned')) {
			// Next, go through the live children.  Only some of these will be listed					
			$liveChildren = $this->owner->liveChildren(true, true);
			if($liveChildren) {
				foreach($liveChildren as $child) {
					$stageChildren->push($child);
				}
			}
		}

		$this->owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren, $context); 
		
	} else {
		user_error("Hierarchy::AllChildren() Couldn't determine base class for '{$this->owner->class}'", E_USER_ERROR);
	}
	
	return $stageChildren;
}

 

The code basically fetches all the children of the current page including pages from the live site if the page in question has the Versioned extension. However the line that interests us the most is the one that reads $this->owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren, $context).

Extending functionality in SilverStripe

Every time you find a call to the method extend in core or in modules is a chance for you to hook in with an extension or a decorator and change that functionality. The first argument of extend is the name of the method that will be called on the extension and any additional arguments will be sent along as arguments to the extending function. There are two extension classes to choose from, namely Extension and DataObjectDecorator. DataObjectDecorator can only be used to extend classes which in turn are sub classes of DataObject. It has some extra functionality which allows you to add database fields and relationships.

In this case we're not interested in the extra functionality but since we're dealing with sub classes of DataObject I'm going to use DataObjectDecorator as my base class. The decorator looks like this:

 

class SiteTreeSorter extends DataObjectDecorator {
	public function augmentAllChildrenIncludingDeleted($stageChildren, $context) {
		if (property_exists($this->owner, "customSort") || $this->owner->hasMethod("getCustomSort")) {
			$customSort = (property_exists($this->owner, "customSort")) ? (array)$this->owner->customSort : (array)$this->owner->getCustomSort();
			$sortDir = (count($customSort) > 1) ? array_pop($customSort) : "ASC";
			$stageChildren->sort($customSort[0], $sortDir);
		}
	}
}

The decorator looks for a property on the class called $customSort. $customSort  can either be an array that looks like this array($column, $sortDir) or a string for the column name, for example "Created".

In order to activate this functionality add the following line to your _config.php

 

Object::add_extension('Page', 'SiteTreeSorter'); 

 

After you've flushed your site (append ?flush=all to the URL) you will be able to add the $customSort property to your page types and any sub pages to those page types will be sorted in the order you define.

There are two limitations with this approach however. The first one is that when the user creates a new page it will still end up in the bottom of the list, not wherever it should according to the new sorting rule. Secondly drag and drop reordering will no longer have an effect. After reloading the CMS the pages will still be in the order you defined, not in the order that the user sorted them in. I'm not sure how or if this can be fixed but if someone has any ideas please post them in the comments!

Marcus Dalgren avatar

Marcus Dalgren

Marcus is a web developer currently working at a small startup company called Overlay in Gothenburg, Sweden. He also does freelance work for small to medium sized businesses in the Gothenburg area.

  • schellmax
    14/02/2011 6:44pm (4 years ago)

    nice, thanks
    i'd also be interested in a solution to have pages arranged in another position than at the bottom of the current tree, instantly when they are created.
    must be some javascript-magic though, and i couldn't find my way through the heavy js cloud on the cms side.

  • Marcus Dalgren
    14/02/2011 6:47pm (4 years ago)

    Yeah I'm thinking about giving that one another go but like you said, the amount of js to go through is kind of staggering.

  • jcwacky
    06/04/2011 11:12am (3 years ago)

    I just cannot get this working. I'm using 2.4.5 and have done the following:
    - Created SiteTreeSorter.php in mysite/code with the above class
    - Added Object::add_extension('Page', 'SiteTreeSorter'); to _config.php
    - In my NewsHolder.php I've added "static $customSort = array('Title','ASC');" in the model.

    But it's not having any effect, the children of the NewsHolder do not change to alphabetical order.

    Any ideas? Anyone able to provide a working example?

  • Marcus Dalgren
    06/04/2011 11:41am (3 years ago)

    Hey jWacky I'll try to help you out later today. I haven't tried the solution in 2.4.5 (I think) but it should work. I'll try to get back to you here later today with a working example.

  • mck
    11/04/2011 8:04pm (3 years ago)

    Hey Marcus, thanks for this tutorial. It's exactly what I was looking for.

    I've been having some trouble with it though.
    No matter what I set $customSort as, I get an error saying "Undefined offset: 0" from the last line in the SiteTreeSorter class.

    I'm probably missing something really obvious but I just can't see it.

    Anyone have any ideas?

  • Marcus Dalgren
    11/04/2011 10:25pm (3 years ago)

    Hey Mck could you try doing a var_dump() on $customSort in SiteTreeSorter just before it sets $sortDir (after line 4 in the code example) and tell me what it says?

  • Marcus Dalgren
    11/04/2011 10:37pm (3 years ago)

    @jWacky I tried out a really basic example with 2.4.5 today and it works for me. Could you perhaps send me your code or something?

  • mck
    12/04/2011 7:41pm (3 years ago)

    The var_dump() returns array(0){}.

    But changing the $customSort from a static variable to a public variable works. I don't really understand why, but at least it works.

  • Marcus Dalgren
    12/04/2011 7:43pm (3 years ago)

    Oh sorry I didn't explain that. It's looking for an instance variable since $this->owner is an object instance. Static methods can't be called dynamically in the same way so it had to be instanced.

    Glad it worked out for you!

  • mck
    12/04/2011 7:49pm (3 years ago)

    Ah, ok. I'll figure this stuff out eventually!

    Thanks for your help.

  • Neelam
    04/06/2013 7:02am (1 year ago)

    I used this in ss 3.0 (DataExtension in place of DataObjectDecorator) to hide/show pages of a certain page type based on a settings variable..It works but the site tree is not shown updated even if I visit via ajax link..it only reflects on a full reolad of the page...
    please tell me how I can get the refreshed site tree on saving settings/visiting via ajax links?
    TreeDropDownField shows the updated pages...

  • jcwacky
    17/07/2013 6:58pm (1 year ago)

    Is anything else required to get this working on 3.1?

    I've tried:

    Object::add_extension('ClientPageHolder', 'SiteTreeSorter');

    <?php
    class SiteTreeSorter extends DataExtension {
    public function augmentAllChildrenIncludingDeleted($stageChildren, $context) {
    $stageChildren->sort("Title");
    }
    }

    But nothing happens! :(

  • Marcus Dalgren
    18/07/2013 10:23am (1 year ago)

    Unfortunately I haven't looked at this at all in 3.x.
    You'd have to track down how the page tree is populated which is what I did in 2.4.x and then hook in there.

    Same goes for showing/hiding. You'd likely have to write some (or alot of) js to get that working properly.

Post a comment ...

You cannot post comments until you have logged in. Login Here.

Advertisement

Site of the Month

Find SSbits on

Top Contributers

Rank Avatar Name
1 article image Aram Balakjian
2 article image Daniel Hensby
3 article image Marcus Dalgren
4 article image Hamish Campbell
5 article image Ty Barho
6 article image Martijn van Nieuwenhoven
7 article image Darren-Lee
8 article image Roman Schmid
9 article image Matt Clegg
10 article image dalesaurus

View full leaderboard


Advertisement