Drupal 7 tip: voeg overal contextual links toe

Posted by: 
Dominique De Cooman

In drupal 7 hebben we iets dat contextual links heet. Het is het kleine wiel dat u ziet wanneer u over blocks zweeft zodat u ze ter plekken kunt bewerken. Het is een goede gebruiks verbetering maar het is niet altijd duidelijk hoe u ze moet implementeren.

De functionaliteit van contextual links was een contrib module in d6 en is nu een core module.

Het hergebruiken van reeds bepaalde local tasks op een custom element

Bijvoorbeeld op een recent project hebben we een lijst van links per thema. De links zijn gegroepeerd door een theme node, we gebruiken een link field om de links vast te houden die geprint moeten worden. Nu zouden we graag contextual links in on kleine block willen zodat we de node kunnen bewerken en onze links kunnen veranderen. Hier volgt hoe u dat kunt doen:

<?php
  
//We have a render array defined in a custom module
  
$block['#theme_wrappers'] = array('thema_block');
  
$block['title'] = l($node->title$url);
  
$block['class'] = "themablock themablock-" $block_count " num-" $block_in_row;
  
$block['more'] =  theme('more_link', array('title' => 'Read more''url' => $url));         

  
//The contextual links we place on our element
  
$block['#contextual_links']['thema_blocks'] = array('node', array($node->nid));  
  
  
render $block;
?>

Laat me uitleggen wat er gebeurd. Alle links met een type MENU_LOCAL_TASK (zoals bepaalt in hun respective hook_menu) en een context MENU_CONTEXT_INLINE die ten grondslag liggen van de path node worden opgehaald. De eenvoudige enry in de #contextual_links array zal alle node local taks ophalen.
Bekijk hoe de node module node/%node/edit en node/%node/delete bepaalt en het zal duidelijk zijn.

<?php
  $items
['node/%node/edit'] = array(
    
'title' => 'Edit',
    
'page callback' => 'node_page_edit',
    
'page arguments' => array(1),
    
'access callback' => 'node_access',
    
'access arguments' => array('update'1),
    
'weight' => 0,
    
'type' => MENU_LOCAL_TASK,
    
'context' => MENU_CONTEXT_PAGE MENU_CONTEXT_INLINE,
    
'file' => 'node.pages.inc',
  );
  
$items['node/%node/delete'] = array(
    
'title' => 'Delete',
    
'page callback' => 'drupal_get_form',
    
'page arguments' => array('node_delete_confirm'1),
    
'access callback' => 'node_access',
    
'access arguments' => array('delete'1),
    
'weight' => 1,
    
'type' => MENU_LOCAL_TASK,
    
'context' => MENU_CONTEXT_INLINE,
    
'file' => 'node.pages.inc',
  );
?>

Dit betekent dat u niet elke link kunt ophalen die in hook_menu bepaalt is, alleen de local task met een context inline werkt voor contextual links.

Vervolgens hebben we een hook theme nodig om onze elements theme wrapper te implementeren.

<?php
/**
 * Implements hook_theme().
 */
function glue_theme() {
  
$items = array(
    
'thema_block' => array(
      
'render element' => 'element',
      
'template' => 'tpl/thema_block',
    ),
  );  
  
  return 
$items;
}
?>

In een preprocess file kunnen we onze variabelen toewijzen.

<?php
/**
 * Implements preprocess_thema_block()
 */
function glue_preprocess_thema_block(&$variables) {
  
$variables['classes_array'][] = $variables['element']['class'];
  
$variables['title'] = $variables['element']['title'];
  
$variables['content'] = $variables['element']['#children'];
  
$variables['more'] = $variables['element']['more'];
}
?>

Het enige wat we nodig hebben is een klein template file om het allemaal te printen.

<?php
<div class="<?php print $classes; ?>" <?php print $attributes?>>
  <?php print render($title_prefix); ?>
  <h2 <?php print $title_attributes?>><?php print $title;?></h2>
  <div class="content"<?php print $content_attributes?>>
    <?php print $content ?>
  </div>
  <?php print $more ?>
  <?php print render($title_suffix); ?>
</div>
?>

Hier kunnen we zien dat we de $classes hebben geprint die de contextual link classes zal bevatten. De contextual links zijn ingevuld door de render functie. De eigenlijke links vindt u in $title_prefix, tevens op hun plek gezet door de render functie. Door die array te renderen zal de html voor de links geprint worden. De jquery die door de contextual_links module is toegevoegd zal alle arrays met de juiste classes transormeren naar het kleine wiel dat u kunt klikken.

Uw eigen contextual links
Als we nu onze eigen contextual links willen moeten we een hook_menu creëren en onze eigen items als local tasks bepalen met een inline context. Hier is een voorbeeld voor het toevoegen van een nieuwe contextual links pagina op http://drupal.org/node/1089922

In het voorbeeld zullen we onze links toevoegen in de hook_menu en met een hook_block_view_alter we zullen de render array veranderen van de blocks en onze links eraan toevoegen.

<?php
  
// An example contextual link menu item.
  
$items['contextual/%/information'] = array(
    
'title' => 'Block information',
    
'type' => MENU_LOCAL_ACTION,
    
'context' => MENU_CONTEXT_INLINE,
    
'page callback' => 'contextual_example_page',
    
'page arguments' => array(1),
    
'access callback' => TRUE,
  );
  
// To use local task menu items, there must be a parent page.
  
$items['contextual'] = array(
    
'title' => 'The contextual example page',
    
'page callback' => 'contextual_example_page',
    
'page arguments' => array(1),
    
'access callback' => TRUE,
  );
?>

<?php
  
/**
  * Implements hook_block_view_alter().
  */
  
function contextual_example_block_view_alter(&$data$block) {
    
// Contextual links can be added as a renderable element to the content of
    // a render array. We check if the block has content, and if so add a
    // contextual link to it.
    
if (isset($data['content']) && is_array($data['content'])) {
      
$contextual_links = array(
        
'contextual',
        array(
$block->module),
      );

      
$data['content']['#contextual_links']['contextual_example'] = $contextual_links;
  }
}
?>

Zoals u kunt zien werkt dit perfect. Het pad in

<?php
$contextual_links 
= array('contextual', array($block->module));
?>

verwijst naar wat we bepaald hebben in het hook_menu haalt alles op onder het pad 'contextual' en de $block->module is het doorgegeven argument.

Als we deze links aan ons custom element willen toevoegen in ons vorige voorbeeld is het enige wat we moeten doen is ze toevoegen aan de array.

<?php
  
//We have a render array defined in a custom module
  
$block['#theme_wrappers'] = array('thema_block');
  
$block['title'] = l($node->title$url);
  
$block['class'] = "themablock themablock-" $block_count " num-" $block_in_row;
  
$block['more'] =  theme('more_link', array('title' => 'Read more''url' => $url));         

  
//The contextual links we place on our element
  
$block['#contextual_links']['thema_blocks'] = array('node', array($node->nid));
  
  
$block['#contextual_links']['whatever'] = array('contextual', array($something_usefull));
  
//On $something_usefull you ll need to put something so your function contextual/%/information  knows what to do in the given context.
  
  
render $block;
?>

Wijzigen
Een andere methode om contextual links toe te voegen is de later methode. Dit is afkomstig van de api page

function hook_menu_contextual_links_alter(&$links, $router_item, $root_path) {
// Voeg een link toe aan alle contextual links voor nodes.

<?php
  
if ($root_path == 'node/%') {
    
$links['foo'] = array(
      
'title' => t('Do fu'), 
      
'href' => 'foo/do'
      
'localized_options' => array(
        
'query' => array(
          
'foo' => 'bar',
        ),
      ),
    );
  }
}
?>

Natuurlijk werkt dit aleen bij reeds bestaande paths on existing elements.

Over views rows
We kunnen ook contextual links toevoegen aan views rows. In ons eerste voorbeeld willen we een contextual link toevoegen aan onze slides van onze slideshow view. Hier volgt wat we deden:

<?php
/**
 *  Contextual links maker
 */
function glue_make_contextual_links($output$nid) {
  
$render_array =
      array(
        
'children' => $output,
        
'#theme_wrappers' => array('contextual_container'),
        
'#contextual_links' => array(
          
'glue' => array('node', array($nid)),
        ),
  );
  return 
render($render_array);
}

/**
 * Adds contextual links to views templates
 */
function glue_preprocess_views_view_field(&$vars) {
  if (isset(
$vars['field']->field_info['field_name']) && $vars['field']->field_info['field_name'] == 'field_slide_image') {
    
$vars['output'] = glue_make_contextual_links($vars['output'], $vars['row']->nid);
  }
}
?>

We implementeerde de views_view_field preprocess hook en we wikkelde de contextual links rond ons veld dat we in de interface weergeven. Daar de slinde in de slideshow een node is kunnen we de nid als argument gebruiken om voor de juiste contextual links op te roepen wat ons in staat stelt om de weergegeven slide te bewerken/verwijderen.

Om volledig te zijn hier volgt de theme_hook en het preprocess

<?php
/**
 * Implements hook_theme().
 */
function glue_theme() {
  
$items = array(
    
'contextual_container' => array(
      
'render element' => 'element',
      
'template' => 'tpl/contextual_container',
    ),
  );  
  
  return 
$items;
}

/**
 * Implements hook_preprocess_contextual_container()
 */
function glue_preprocess_contextual_container(&$variables) {
  
$variables['content'] = $variables['element']['children'];
}
?>

En de template file:

<?php
<div class="<?php print $classes; ?>" <?php print $attributes?>>
  <div <?php print $content_attributes?>>
    <?php print $content ?>
  </div>
  <div class="custom-contextual-links">
    <?php print render($title_suffix); ?>
  </div>
</div>
?>

In het volgende voorbeeld pakte we het anders aan. We hebben een tabel en we willen een contextual link veld toevoegen. We voegde een node edit link veld aan onze view table display toe. In de template van dat genoemde veld : hebben we het volgende ingevoerd:

<?php
<div class="contextual-links-region">
  <
div class="contextual-links-wrapper">
    <
ul class="contextual-links">
      <
li>
        <?
php print $output?>
      </li>
    </ul>  
  </div>
</div>
?>

Deze is minder clean omdat u slechts een zo'n item kunt toevoegen. Theoretisch gezien zou het mogelijk moeten zijn om het aan de tabel rij toe te voegen maar dat betekent dat de jquery aangepast moet worden van contextual links en tevens op tr elements gericht is, in de core is het alleen gericht op div elements, om ze rond tr elements te plaatsen is geen geldige html.

Over models
Op onze site gebruiken we het model module die "een container enity" levert. Het is een entity met alleen een titel en zijn fieldable. In plaats van vele hooks zelf te implementeren kunt u deze module gebruiken en deze entity meteen laten werken.(http://drupal.org/project/model) We gebruiken de model entity store informatie die niet de extra's nodig heeft die een node biedt, like workflow, authoring, etc.. We willen alleen dingen in velden opslaan. Perfect om header images en het pad dat ze nodig hebben om op afgebeeld te worden op te slaan. Door de hearder_image bundle te gebruiken kunnen we een andere header weergeven op de specifieke paden. Nu dat gedaan is zou het niet geweldig zijn om er contextual links op te hebben? Dit is hoe we het gedaan hebben:

<?php
function glue_menu() {
  
$items['admin/content/models/model/%/add'] = array(
    
'title' => 'Add',
    
'type' => MENU_LOCAL_ACTION,
    
'context' => MENU_CONTEXT_INLINE,
    
'page callback' => 'glue_model_add_type',
    
'page arguments' => array(4),
    
'access callback' => TRUE,
  );

  return 
$items;
}

/**
 * goto the adding page for the model
 */
function glue_model_add_type($mid) {
  
$destination drupal_get_destination();
  
$query db_select('model''m');
  
$result $query
          
->condition('m.model_id'$mid'=')
          ->
fields('m', array('type'))
          ->
execute()
          ->
fetch();

  
$params = array(
    
'query' => array(
      
'destination' => $destination['destination'],
    )
  );
  
//where to go next
  
$_GET['destination'] = url('admin/content/models/add/' $result->type$params);
  
drupal_goto('admin/content/models/add/' $result->type$params);
}

function 
create_logo() {
  
$model model_load($item->model_id);
  
$logo field_view_field('model'$model'field_logo_logo''full');
  
$logo[0]['#image_style'] = 'logo_style';
  
$logo_and_link = array(
    
'#type' => 'link',
    
'#title' => render($logo[0]),
    
'#href' => '',
    
'#options' => array('html' => TRUE'attributes' => array('class' => $css_class)),
    
'#contextual_links' => array(
      
'logo' => array('admin/content/models/model', array($model->model_id)),
    )
  );
  if (isset(
$model->field_logo_link['und'][0]['url'])) {
    
$header_and_link['#href'] = $model->field_logo_link['und'][0]['url'];
  }
  return 
$logo_and_link;
}
?>

We hebben een menu menu callback toegevoegd om locale acties te registreren om elk model toe te voegen. In de callback zoeken we naar het type en sturen het naar de add page. Wanneer we op deze manier een render array creëren zal de contextyal links niet aleen bewerken en verwijderen maar zal er ook een toevoegen link aanwezig zijn.

Tenslotte hoe voeg je de node/add/%type in een contextual link trick

Dezelfde truc die we met de models deden kunnen we ook met de nodes doen:

<?php
function glue_menu() {
  
$items['node/%/add'] = array(
    
'title' => 'Add',
    
'type' => MENU_LOCAL_ACTION,
    
'context' => MENU_CONTEXT_INLINE,
    
'page callback' => 'glue_node_add_type',
    
'page arguments' => array(1),
    
'access callback' => TRUE,
  );

  return 
$items;
}

/**
 * goto the adding page for the node
 */
function glue_node_add_type($nid) {
  
$destination drupal_get_destination();
  
$query db_select('node''n');
  
$result $query
          
->condition('n.nid'$nid'=')
          ->
fields('n', array('type'))
          ->
execute()
          ->
fetch();

  
$params = array(
    
'query' => array(
      
'destination' => $destination['destination'],
    )
  );
  
//where to go next
  
$_GET['destination'] = url('node/add/' $result->type$params);
  
drupal_goto('node/add/' $result->type$params);
}
?>

De slides zijn nodes dus in het bovenstaande screenshot kunt u "Toevoegen". Dit is veroorzaakt door het laatste deel van de code.

Ik weet zeker dat er vele andere methodes zijn om contextual links toe te voegen. Dus als je er weet, post ze in de comments.

Reactie toevoegen