This module provides a convenient API to create sub-pages for entity urls.
E.g. node/%/about, or taxonomy/term/%/related.

Sub-page definitions can be specific to an entity bundle or any other subset of entities.
Different modules can independently define sub-pages on the same route, e.g. node/%/about, each with their own logic for page title, access check, tab and navigation settings, and page delivery.

It is recommended to use this in combination with Display suite and Views content DS, so you can easily embed views displays mixed with other fields. E.g. node/%/people could have a list of all contributors to a wiki page, or all members of an organic group, etc.

Benefits over Ctools page variants

  1. All in code, no clicking around -> easier to deploy.
  2. Two modules can negotiate over the same router path, without knowing each other.
    (If you would export CTools page variants as a feature, that feature would have to contain all definitions for that path - afaik)

Benefits over Taxonomy display and Taxonomy Views Integration (TVI)

  1. All in code, no clicking around -> easier to deploy.
  2. Works for every entity type.
  3. Different behavior (sub-pages, access conditions, title logic, etc) per bundle or per other subset of entities.
    E.g. you could have a different page callback or entity view mode for products with price > 1000 EUR.
  4. Allows any page callback, not just views or entity view mode rendering.
    (but entity view mode rendering is made especially comfortable)
  5. You can have a different tabs (local tasks) for different node types / bundles - with different order, different captions, etc.
  6. Does not mess with wildcard loaders in the menu_router table. Modules that expect node_load() for node/%/.. will not be confused.

In fact it should be possible to rewrite TVI and Taxonomy display as implementations of entity aspect.

Example

(this is a quick write-up, and will be improved over time)

Use case:
- You have a taxonomy vocabulary "Interests", where nodes (type "article") and users can be tagged.
- You have a taxonomy vocabulary "Image tags", where nodes (type "image") can be tagged.

You want, for "Interests" terms:
- taxonomy/term/% to list the articles in a normal list view with teasers.
- taxonomy/term/%/people to list the users that are tagged with these terms. Access should be restricted with a custom access callback. Title should be "People involved"

You want, for "Image tags" terms:
- taxonomy/term/% to list the images in a grid view with lightbox.
- taxonomy/term/%/people to show the text from a field. Access = everyone. Title should be "Photography team".

To spice it up:
- Local tasks (tabs) on "Image tags" should have a different order.

You want to cleanly separate these two features, so you want
- one module for "interests" stuff
- another module for "image" stuff.

Options:

  • views page displays?
    You would need two separate views on the same router path -> not possible.
  • views displays attached as fields on view modes "full" -> not bad!
    Caveats:
    - Taxonomy will add some junk to the end of the view mode display, that is hard to remove.
    - What about the taxonomy/term/%/people page? You would need a separate router item and view mode for that.
  • Use hook_entity_info_alter() to define a custom view mode, and hook_menu() for a page callback on taxonomy/term/%/people, that renders the term with this view mode. -> awesome!
    Caveats:
    - How can you have a separate access mechanics and title?
    - How can we rearrange the tabs?
    - Which module is the "owner" of taxonomy/term/%/people? Which module does all this magic? Remember, originally we wanted this to be two separate modules, for easier reuse. Now whichever of the two modules does this, the other will depend on it.
    - Working with hook_entity_info_alter() and hook_menu() is ok, but not pleasant.
  • Using panel page variants: Ok, but..
    - That's config living in the db, which needs to be exported to features. Features are ok, but don't make a nice diff in git, etc.
    - You can't have your clean separation of Image stuff vs Interests stuff, because these share the same paths.

Now, how to do this with entityaspect?
- Install the module
- Create one custom module for interests, another for images.
- Let both modules implement hook_entityaspect()
- Let both modules implement hook_entityaspect_page__taxonomy_term()
- Create views displays that can be put in view modes. E.g. with EVA or with views_content_ds, or with DS custom fields.
- Configure the view modes to make the views displays show up. (Display suite is your friend).

Custom module code:

function my_interests_entityaspect($api) {
  // All of our settings apply to the taxonomy_term entity type.
  $api->type('taxonomy_term')
    // Let entityaspect override this path with hook_menu_alter().
    ->hijackPage('taxonomy/term/%')
    // Let entityaspect register an additional path with hook_menu()
    ->page('taxonomy/term/%/people')
    // Let entityaspect register a view mode for the taxonomy_term entity type.
    ->viewMode('people', 'People')
  ;
}

function my_images_entityaspect($api) {
  $api->type('taxonomy_term')
    // Any number of modules can register the same paths.
    // This does not result in a conflict.
    ->hijackPage('taxonomy/term/%')
    ->page('taxonomy/term/%/people')
    // We give the view mode a different admin display name here.
    // So it is not clearly defined whether it will be "People" or "Photographers".
    // Who cares..
    ->viewMode('people', 'Photographers')
  ;
}

function my_interests_entityaspect_page__taxonomy_term($api, $term, $route) {
  if ($term->vocabulary_machine_name == 'interests') {
    switch ($route) {
      case 'taxonomy/term/%':
        // No access check, this is public.
        $api->viewMode('full');
        $api->title(t('Articles'));
        break;
      case 'taxonomy/term/%/people':
        if (my_interests_people_access()) {
          $api->viewMode('people');
          $api->title(t('People involved'));
        }
        break;
    }
  }
}

function my_images_entityaspect_page__taxonomy_term($api, $term, $route) {
  if ($term->vocabulary_machine_name == 'image_tags') {
    switch ($route) {
      case 'taxonomy/term/%':
        // No access check, this is public.
        $api->viewMode('full');
        $api->title(t('Images'));
        break;
      case 'taxonomy/term/%/people':
        // No access check, this is public.
        $api->viewMode('people');
        $api->title(t('Photographers'));
        break;
    }
  }
}

History

Entity aspect is the D7 predecessor of D6 http://drupal.org/project/nodeaspect, now for any entity type.

Project information

Releases