Sort CP Entries by Category I know it's possible, using modifyEntrySortableAttributes, to sort the CP entries table by a custom field, when that field is a number, string, date/time, or bool. Would it be possible to sort a CP table by category? If not, is there another hook, or some other way I could implement this functionality?


Sorting by number, string, date/time, bool:
In Craft 2.5, how can you sort the entry index page by a custom field?
This is probably not the answer you are looking for but it works. You can change the Craft.BaseElementIndexand the updateElements function. All you have to do is to change the destination path to a custom controller

Craft.BaseElementIndex.prototype.updateElements = function()
{
    // Ignore if we're not fully initialized yet
    if (!this.initialized)
    {
        return;
    }

    this.setIndexBusy();

    var params = this.getViewParams();

    Craft.postActionRequest('yourPluginHandle/controllerName/getElements', params, $.proxy(function(response, textStatus)
    {
        this.setIndexAvailable();

        if (textStatus == 'success')
        {
            this._updateView(params, response);
        }
        else
        {
            Craft.cp.displayError(Craft.t('An unknown error occurred.'));
        }

    }, this));
},


Then you can create a custom controller that extends the ElementIndexController and can use it's function that calls the private function _getElementResponseData. All the elements that should be displayed are received in

$responseData['html'] = $this-&gt;_elementType-&gt;getIndexHtml(
    $this-&gt;_criteria,
    $disabledElementIds,
    $this-&gt;_viewState, &lt;-- this is the important variable
    $this-&gt;_sourceKey,
    $this-&gt;_context,
    $includeContainer,
    $showCheckboxes
);


The sort order is defined here

if ($sortableAttributes)
{
    $order = (!empty($viewState['order']) &amp;&amp; isset($sortableAttributes[$viewState['order']])) ? $viewState['order'] : ArrayHelper::getFirstKey($sortableAttributes);
    $sort  = (!empty($viewState['sort']) &amp;&amp; in_array($viewState['sort'], array('asc', 'desc'))) ? $viewState['sort'] : 'asc';

    $criteria-&gt;order = preg_replace('/^(.*?)(?:\s+(?:asc|desc))?(,.*)?$/i', "$1 {$sort}$2", $order);
}


As you can easily see it checks if the $viewState['order'] is empty or not. This variable is defined in your controller so all you have to do is to check if your javascript returns a order variable, get the field by handle and check if the field handle is instance of BaseRelationField. If so, you'll unset the variable in the $viewState and change the $criteria by yourself. 

Unfortunately the query is executed in BaseElementType.php so you have to implement a way to sort by eager loading elements (which I doubt is possible by default) or you can create a custom element type just for this case and overwrite the entire getIndexHtml() function.

It might sound like a lot of work and effort but honestly it can be done in 5-10 minutes... I've done the same once within no time. It's 99% copy paste. Maybe someone  has a better answer but since there is no real event or hook I'm not sure if there is a real clean way to do this. Of course this is only for Craft 2. In Craft 3 it's much easier since there are more events and hooks and you can replace classes more easy.

Another approach would be to overwrite the entire BaseElementIndex class with Yii or this. But I never did that so I can't give much advice