Sorting in ModelAdmin

With this being my first post, I hope it's not too rough. I've been wanting to start posting things I've picked up along the way when building in SilverStripe so other community members can learn from examples like I have. So let's get this started.

The Need:

In many sites I work on there is a need to manage data that will be used on all pages. While some times this is handled by GridField on different page types through a many_many relationship, other times the DataObjects are managed in a ModelAdmin as they'll be used across the site, regardless of page type. An example for this could be utility or footer navigation.

What Are Our Options:

In the case of navigation there are a couple solutions, some which have already been built by the community. Heyday's Menu Manager module does this well already, but for this exercise we'll forgo the module and write some code.

What We'll Need:

When it comes to managing data in the ModelAdmin we're still using a GridField. The main difference is it's not based on a relation like it would be for managing related objects (i.e. Page has_many Slides). Even though this is the case, we can still utilize tools to modify the GridField to achieve enhanced functionality. In this example we'll be utilizing the GridField Extensions module. The module adds a lot of enhancements to a GridField, but for now we'll be using GridFieldOrderableRows.

Get To The Code Already:

Let's get some things setup, starting with the Navigation Item.

NavigationItem.php

<?php

class NavigationItem extends DataObject
{

    private static $singular_name = 'Navigation Item';
    private static $plural_name = 'Navigation Items';

    private static $db = array(
        'Title' => 'Varchar(255)',
        'Active' => 'Boolean'
    );

    private static $has_one = array(
        'Link' => 'SiteTree'
    );

    private static $summary_fields = array(
        'Title',
        'Link.Title',
        'Active.Nice'
    );

    private static $field_labels = array(
        'Title' => 'Title',
        'Link.Title' => 'Link',
        'Active.Nice' => 'Active'
    );

    public function getCMSFields()
    {
        $fields = parent::getCMSFields();

        return $fields;
    }

    public function canCreate($member = null)
    {
        return true;
    }

    public function canEdit($member = null)
    {
        return true;
    }

    public function canDelete($member = null)
    {
        return true;
    }

    public function canView($member = null)
    {
        return true;
    }

}

Now the managing ModelAdmin

NavigationModelAdmin.php

<?php

class NavigationAdmin extends ModelAdmin
{

    private static $managed_models = array(
        'NavigationItem'
    );

    private static $url_segement = 'nav-items';
    private static $menu_title = 'Navigation';

}

That's all well and good. You'll have the model admin that manages the NavigationItem DataObjects, but we want to take it a step further and add the ability to sort those items. We'll first need to update our NavigationItem object to allow the sort to be stored and set the default sort.

    private static $db = array(
        'Title' => 'Varchar(255)',
        'Active' => 'Boolean',
        'Sort' => 'Int'
    );

    private static $has_one = array(
        'Link' => 'SiteTree'
    );

    private static $default_sort = 'Sort';

Now that our NavigationItem is all set we can focus on updating our ModelAdmin.

    public function getEditForm($id = null, $fields = null)
    {
        $form = parent::getEditForm($id, $fields);
        // $gridFieldName is generated from the ModelClass, eg if the Class 'Product'
        // is managed by this ModelAdmin, the GridField for it will also be named 'Product'
        $gridFieldName = $this->sanitiseClassName($this->modelClass);
        if ($gridFieldName == 'NavigationItem') {
            $gridField = $form->Fields()->fieldByName($gridFieldName);
            $gridField->getConfig()->addComponent(new GridFieldOrderableRows());
        }

        return $form;
    }

Let's break down what's going on here. If we look at the ModelAdmin class there is a getEditForm() function. This function essentially creates the GridField for the model class that is currently loaded in the cms. By overriding this function we must make sure we're calling the parent function so we don't lose anything. This is similar to how you call parent::getCMSFIelds() on classes extending Page (and some times DataObject). The result is the CMSForm with the GridField in it.

Since you can manage multiple models in a single ModelAdmin class we need to figure out what class we're managing. $this->modelClass holds the current class being managed. We sanitize the model class and then check if it matches the ClassName of the object we want to sort. Even though we're only managing a single class in this example I've found it's better to still check the class name as you or another developer may add more managed models to the ModelAdmin at a later time.

Once we know we're working with the proper class, we get the GridField from the form by name. We then chain ->getConfig()->addComponent(new GridFieldOrderableRows()) to add the GridField component to the existing config. Once that's all done we simply return the form.

Our final code looks like this:

NavigationItem.php

<?php

class NavigationItem extends DataObject
{

    private static $singular_name = 'Navigation Item';
    private static $plural_name = 'Navigation Items';

    private static $db = array(
        'Title' => 'Varchar(255)',
        'Active' => 'Boolean',
        'Sort' => 'Int'
    );

    private static $has_one = array(
        'Link' => 'SiteTree'
    );

    private static $default_sort = 'Sort';

    private static $summary_fields = array(
        'Title',
        'Link.Title',
        'Active.Nice'
    );

    private static $field_labels = array(
        'Title' => 'Title',
        'Link.Title' => 'Link',
        'Active.Nice' => 'Active'
    );

    public function getCMSFields()
    {
        $fields = parent::getCMSFields();

        $fields->removeByName(array(
            'Sort'
        ));

        return $fields;
    }

    protected function onBeforeWrite() {
        if (!$this->Sort) {
            $this->Sort = NavigationItem::get()->max('Sort') + 1;
        }

        parent::onBeforeWrite();
    }

    public function canCreate($member = null)
    {
        return true;
    }

    public function canEdit($member = null)
    {
        return true;
    }

    public function canDelete($member = null)
    {
        return true;
    }

    public function canView($member = null)
    {
        return true;
    }

}

NavigationAdmin.php

<?php

class NavigationAdmin extends ModelAdmin
{

    private static $managed_models = array(
        'NavigationItem'
    );
    private static $url_segment = 'nav-items';
    private static $menu_title = 'Navigation';

    public function getEditForm($id = null, $fields = null)
    {
        $form = parent::getEditForm($id, $fields);
        // $gridFieldName is generated from the ModelClass, eg if the Class 'Product'
        // is managed by this ModelAdmin, the GridField for it will also be named 'Product'
        $gridFieldName = $this->sanitiseClassName($this->modelClass);
        if ($gridFieldName == 'NavigationItem') {
            $gridField = $form->Fields()->fieldByName($gridFieldName);
            $gridField->getConfig()->addComponent(new GridFieldOrderableRows());
        }

        return $form;
    }

}

 The Takeaway:

ModelAdmin, while pretty basic out of the box, can be customized to include many types of enhancements. Getting to know the class you are extending helps you understand what it is that needs to be changed for you to get the desired result.

Post your comment

Comments

No one has commented on this page yet.

RSS feed for comments on this page | RSS feed for all comments