Wie gestalte ich einen Link aus einer Vorlagendatei?

10

Eine Zweigvorlage rendert eine Liste von Links, die mit Klassen geliefert werden. Das Grundlegende:

{{ mylink }}

Zweigcode gibt so etwas wie aus

<a href="#" class="someclass" >the text</a>

Nicht alle Links haben Klassen. Ich möchte eine Zweigvorlage schreiben, die stattdessen Folgendes ausgibt:

<a href="#" class="someclass" >
  <span class="sprite someclass" ></span>
  the text</a>

Was ich versucht habe:

  1. Ich suchte nach der Zweigvorlage, die überschrieben werden sollte. Leider scheinen Links nicht von einer Zweigvorlage gerendert zu werden.

  2. Ich habe versucht, die Zweigvariable wie zu aktualisieren

    set mylink['#title'] = "<span>...</span>" ~ mylink['#title']

    Aber das werde ich nicht tun lassen.

Kunstroboter
quelle
Es muss nur in Zweigvorlage sein? Ich kann das Markup ändern und Klassen über die Benutzeroberfläche festlegen (Inhaltstyp> Anzeigeformular verwalten).
Vagner

Antworten:

6

Hier ist eine Lösung nur für Zweige für ein bestimmtes Gebiet, das diese Behandlung benötigt. Es ist keine generische Lösung für alle Links überall.

some-template.twig:

<ul class="field--name-field-links">
  {% for item in content.field_links %}
  {% if item['#title'] %}
    <li>
      <a href="{{ item['#url'] }}" class="{{ item['#options'].attributes.class|join(' ') }}" >
        {% if item['#options']['attributes']['class'] %}
          <span class="sprite {{ item['#options']['attributes']['class']|join(" ") }}"></span>
        {% endif %}
        {{ item['#title'] }}
      </a>
    </li>
  {% endif %}
  {% endfor %}
</ul>
Kunstroboter
quelle
1
OMG endlich habe ich 2 Tage nach einer Lösung für dieses Problem gesucht. Ich kann immer noch nicht verstehen, wie twig HTML ausgibt, wenn wir es übergeben. Item.link ist ein Array. Hat jemand ein Dokument dafür?
Guillaume Bois
Oh gush ... Leider funktioniert diese Lösung nur teilweise. Ich möchte die Links zum Sprachumschalter ändern und die Verwendung item.link['#url']gibt für alle Sprachen die gleiche URL!
Guillaume Bois
@GuillaumeBois Können Sie drupal.stackexchange.com/a/199998/54619 testen, um festzustellen , ob das Problem des 'Sprachumschalters' gelöst ist? Danke
Vagner
5

Ich habe keine Möglichkeit gefunden, den Link '#markup' in twig zu ändern, aber es gibt eine Möglichkeit, ihn in der Renderphase zu ändern.
Ich habe dieses kleine Modul erstellt, das die Link-Funktionalität erweitert und es ermöglicht, einige Dinge in gerenderte Links einzufügen. Also lass uns etwas Code machen, ich werde es in Kommentaren erklären ...

Moduldateistruktur:

better_link
 | - src
   | - Element
     | BetterLink.php
   | - Plugin
     | - FieldFormatter
       | BetterLinkFormatter.php
 | better_link.info.yml
 | better_link.module

Dateiinhalt:

better_link.info.yml

name: 'Better link'
type: module
package: 'Field types'
description: 'A very nice better link'
core: '8.x'
dependencies:
  - field
  - link

better_link.module

<?php

use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_help().
 * Just some words about the module.
 */
function better_link_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.better_link':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('Provide a improved link formatter and renderer for a custom link markup.') . '</p>';
      $output .= '<p>' . t('Will be added a span html tag right before link content.') . '</p>';
      $output .= '<p>' . t(' - Link class can be added throught manage display.') . '</p>';
      $output .= '<p>' . t(' - Span class can be added throught manage display.') . '</p>';
      return $output;
  }
}

BetterLinkFormatter.php

<?php

/**
 * @file
 * Contains \Drupal\better_link\Plugin\Field\FieldFormatter\BetterLinkFormatter.
 */

namespace Drupal\better_link\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Form\FormStateInterface;
use Drupal\link\Plugin\Field\FieldFormatter\LinkFormatter;

/**
* Plugin implementation of the 'better_link' formatter.
*
* @FieldFormatter(
*   id = "better_link",
*   label = @Translation("Better Link"),
*   field_types = {
*     "link"
*   }
* )
*/
class BetterLinkFormatter extends LinkFormatter {
  /**
   * {@inheritdoc}
   */
  public static function defaultSettings() {
    $settings = parent::defaultSettings();
    //Keeping simple...
    $settings['span_class'] = '';
    $settings['link_class'] = '';
    //... but feel free to add, tag_name, buble_class, wraper_or_inside
    return $settings;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsForm(array $form, FormStateInterface $form_state) {
    $form = parent::settingsForm($form, $form_state);
    //Make sure that you always store a name that can be used as class
    $settings['link_class'] = Html::cleanCssIdentifier(Unicode::strtolower($this->getSetting('link_class')));
    $settings['span_class'] = Html::cleanCssIdentifier(Unicode::strtolower($this->getSetting('span_class')));
    $this->setSettings($settings);

    $form['link_class'] = array(
      '#title' => $this->t('Inject this class to link'),
      '#type' => 'textfield',
      '#default_value' => $settings['link_class'],
    );
    $form['span_class'] = array(
      '#title' => $this->t('Inject this class to span'),
      '#type' => 'textfield',
      '#default_value' => $settings['span_class'],
    );
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function settingsSummary() {
    $summary = parent::settingsSummary();
    //Same here. Somehow if you use setSettings here don't reflect in settingsForm
    $settings['link_class'] = Html::cleanCssIdentifier(Unicode::strtolower($this->getSetting('link_class')));
    $settings['span_class'] = Html::cleanCssIdentifier(Unicode::strtolower($this->getSetting('span_class')));
    $this->setSettings($settings);

    //Summary is located in the right side of your field (in manage display)
    if (!empty($settings['link_class'])) {
      $summary[] = t("Class '@class' will be used in link element.", array('@class' => $settings['link_class']));
    }
    else {
      $summary[] = t('No class is defined for link element.');
    }

    if (!empty($settings['span_class'])) {
      $summary[] = t("Class '@class' will be used in span element.", array('@class' => $settings['span_class']));
    }
    else {
      $summary[] = t('No class is defined for span element.');
    }

    return $summary;
  }

  /**
   * {@inheritdoc}
   */
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = parent::viewElements($items, $langcode);
    //Yeah, here too, same 'problem'.
    $settings['link_class'] = Html::cleanCssIdentifier(Unicode::strtolower($this->getSetting('link_class')));
    $settings['span_class'] = Html::cleanCssIdentifier(Unicode::strtolower($this->getSetting('span_class')));

    foreach ($items as $delta => $item) {
      //Lets change the render element type and inject some options that will
      //be used in render phase
      if (isset($elements[$delta]['#type'])) {
        $elements[$delta]['#type'] = 'better_link';
        $elements[$delta]['#options']['#link_class'] = $settings['link_class'];
        $elements[$delta]['#options']['#span_class'] = $settings['span_class'];
      }
    }
    //Next step, render phase, see ya...
    return $elements;
  }
}

BetterLink.php

<?php

/**
 * @file
 * Contains \Drupal\better_link\Element\BetterLink.
 */

namespace Drupal\better_link\Element;

use Drupal\Core\Render\Element\Link;

/**
 * Provides a better_link render element. Almost the same as link.
 *
 * @RenderElement("better_link")
 */
class BetterLink extends Link {
  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return array(
      '#pre_render' => array(
        array($class, 'preRenderLink'),
      ),
    );
  }

  /**
   * {@inheritdoc}
   */
  public static function preRenderLink($element) {
    //Hello again. Lets work.
    //Before Drupal create the rendered link element lets inject our stuff...
    //...Our class to link
    $element['#options']['attributes']['class'][] = $element['#options']['#link_class'];
    //...Build span classes
    $span_classes = $element['#options']['#span_class'] . ' ' . $element['#options']['#link_class'];
    //...And get rid them.
    unset($element['#options']['#link_class']);
    unset($element['#options']['#span_class']);
    //Lets Drupal do the hard work
    $element = parent::preRenderLink($element);
    //Here is where the magic happens ;)
    if (!empty($element['#markup'])) {
      //Inject our span right before link content.
      $element['#markup'] = str_replace('">', "\"><span class='$span_classes'></span>", $element['#markup']);
      //Side comment - Thank you spaceless, str_replace can be used here
    }
    //Now, whatever you change in your url or another object will not maintain,
    //the only thing that will be returned in the end is
    //$element['#markup'], so this is the only thing you can change.
    return $element;
  }
}

Wichtig:

Dies wird für alle Ihre Arbeit Link Felder , sicher, wenn Sie seine Formatierer ändern in der Anzeige verwalten (Bearbeiten des Knotentypen).

Ich hoffe das kann nützlich sein.

Anfrage an @artfulrobot: Können Sie dieses Modul testen? Ich denke, dass das Übersetzungsproblem auf diese Weise gelöst werden kann.

Vagner
quelle
Ja, danke für eine so lange und detaillierte Antwort. Ich denke, es gibt einen massiven Fehler in der Zweigschicht von d8 mit riesigen PHP-basierten Lösungen für ein einfaches Themenproblem. Aber danke fürs posten, v hilfreich.
Artfulrobot
@artfulrobot Du bist wahrscheinlich in einer besseren Position, um dies zu beantworten als ich - zu welcher der Antworten hier sollte das Kopfgeld gehen?
Clive
@clive danke, aber dein Kopfgeld, dein Anruf. Die Frage, die ich stellte, betraf Twig. Die meisten dieser Antworten beinhalten das Ersetzen oder Erweitern des Kerns durch viel schwieriger zu wartendes PHP. Obwohl ich für die Eingabe dankbar bin und sie Möglichkeiten bieten, die Arbeit zu erledigen, beantworten sie die Frage IMO nicht. Ich fürchte, dieses "einfache" Themenproblem war der Strohhalm, der dem D8 meines sprichwörtlichen Kamels den Rücken gebrochen hat. d völlig aufgeholt: - |
Artfulrobot
Danke @artfulrobot, verstanden. Es ist eine Schande, dass dies keine zufriedenstellendere Schlussfolgerung gezogen hat. Ich werde das Kopfgeld automatisch für alles vergeben lassen, worüber die Community abgestimmt hat
Clive
Zweig ist erstaunlich. Das ganze Problem kommt vom Drupal-Themensystem, was ich für einen falschen Ansatz halte. Überprüfen Sie einfach, wie viel zusätzliche Arbeit Sie erledigen müssen, wenn Sie einen einfachen Link anpassen möchten. Das ist enttäuschend.
Zoltán Süle
4

Sie können #title einfach ein Render-Array hinzufügen, wie:

['#title'] = array('#markup' => '<i class="my-icons">yummy</i>' . $item['content']['#title']);

Alte lange Antwort:

Sie können den Link Generator Service überschreiben

Erstellen Sie ein Modul (alternative_linkgenerator) mit einer Infodatei alternative_linkgenerator.info.yml

name: Alternative LinkGenerator
type: module
description: Adds alternative link generation.
core: 8.x

Erstellen Sie eine Datei mit dem Namen alternative_linkgenerator.services.yml

services:
  alternative_linkgenerator.link_generator:
    class: Drupal\alternative_linkgenerator\AlternativeLinkGenerator

Als Nächstes erstellen Sie die Klasse, fügen einen Ordner mit dem Namen "src" (gemäß den PSR-4-Standards für das automatische Laden) und eine Datei mit dem Namen "AlternativeLinkGenerator.php" hinzu. (Dies ist eine 1: 1-Kopie, Sie müssen die Dinge für Ihre anpassen)

class AlternativeLinkGenerator extends LinkGeneratorInterface {

  /**
   * The url generator.
   *
   * @var \Drupal\Core\Routing\UrlGeneratorInterface
   */
  protected $urlGenerator;

  /**
   * The module handler firing the route_link alter hook.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * Constructs a LinkGenerator instance.
   *
   * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
   *   The url generator.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer service.
   */
  public function __construct(UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler, RendererInterface $renderer) {
    $this->urlGenerator = $url_generator;
    $this->moduleHandler = $module_handler;
    $this->renderer = $renderer;
  }

  /**
   * {@inheritdoc}
   */
  public function generateFromLink(Link $link) {
    return $this->generate($link->getText(), $link->getUrl());
  }

  /**
   * {@inheritdoc}
   *
   * For anonymous users, the "active" class will be calculated on the server,
   * because most sites serve each anonymous user the same cached page anyway.
   * For authenticated users, the "active" class will be calculated on the
   * client (through JavaScript), only data- attributes are added to links to
   * prevent breaking the render cache. The JavaScript is added in
   * system_page_attachments().
   *
   * @see system_page_attachments()
   */
  public function generate($text, Url $url) {
    // Performance: avoid Url::toString() needing to retrieve the URL generator
    // service from the container.
    $url->setUrlGenerator($this->urlGenerator);

    if (is_array($text)) {
      $text = $this->renderer->render($text);
    }

    // Start building a structured representation of our link to be altered later.
    $variables = array(
      'text' => $text,
      'url' => $url,
      'options' => $url->getOptions(),
    );

    // Merge in default options.
    $variables['options'] += array(
      'attributes' => array(),
      'query' => array(),
      'language' => NULL,
      'set_active_class' => FALSE,
      'absolute' => FALSE,
    );

    // Add a hreflang attribute if we know the language of this link's url and
    // hreflang has not already been set.
    if (!empty($variables['options']['language']) && !isset($variables['options']['attributes']['hreflang'])) {
      $variables['options']['attributes']['hreflang'] = $variables['options']['language']->getId();
    }

    // Ensure that query values are strings.
    array_walk($variables['options']['query'], function(&$value) {
      if ($value instanceof MarkupInterface) {
        $value = (string) $value;
      }
    });

    // Set the "active" class if the 'set_active_class' option is not empty.
    if (!empty($variables['options']['set_active_class']) && !$url->isExternal()) {
      // Add a "data-drupal-link-query" attribute to let the
      // drupal.active-link library know the query in a standardized manner.
      if (!empty($variables['options']['query'])) {
        $query = $variables['options']['query'];
        ksort($query);
        $variables['options']['attributes']['data-drupal-link-query'] = Json::encode($query);
      }

      // Add a "data-drupal-link-system-path" attribute to let the
      // drupal.active-link library know the path in a standardized manner.
      if ($url->isRouted() && !isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
        // @todo System path is deprecated - use the route name and parameters.
        $system_path = $url->getInternalPath();
        // Special case for the front page.
        $variables['options']['attributes']['data-drupal-link-system-path'] = $system_path == '' ? '<front>' : $system_path;
      }
    }

    // Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags()
    // only when a quick strpos() gives suspicion tags are present.
    if (isset($variables['options']['attributes']['title']) && strpos($variables['options']['attributes']['title'], '<') !== FALSE) {
      $variables['options']['attributes']['title'] = strip_tags($variables['options']['attributes']['title']);
    }

    // Allow other modules to modify the structure of the link.
    $this->moduleHandler->alter('link', $variables);

    // Move attributes out of options since generateFromRoute() doesn't need
    // them. Include a placeholder for the href.
    $attributes = array('href' => '') + $variables['options']['attributes'];
    unset($variables['options']['attributes']);
    $url->setOptions($variables['options']);

    // External URLs can not have cacheable metadata.
    if ($url->isExternal()) {
      $generated_link = new GeneratedLink();
      $attributes['href'] = $url->toString(FALSE);
    }
    else {
      $generated_url = $url->toString(TRUE);
      $generated_link = GeneratedLink::createFromObject($generated_url);
      // The result of the URL generator is a plain-text URL to use as the href
      // attribute, and it is escaped by \Drupal\Core\Template\Attribute.
      $attributes['href'] = $generated_url->getGeneratedUrl();
    }

    if (!SafeMarkup::isSafe($variables['text'])) {
      $variables['text'] = Html::escape($variables['text']);
    }
    $attributes = new Attribute($attributes);
    // This is safe because Attribute does escaping and $variables['text'] is
    // either rendered or escaped.
    return $generated_link->setGeneratedLink('<a' . $attributes . '>' . $variables['text'] . '</a>');
  }

}

Bearbeiten Sie services.yml (normalerweise unter sites / default / services.yml in Ihrer Drupal 8-Codebasis) und fügen Sie Folgendes hinzu:

  services:
    link_generator:
      alias: alternative_linkgenerator.link_generator

Requisiten geht hier

rémy
quelle
Danke, ich werde es versuchen. Ich möchte aber nur, dass es das in bestimmten Kontexten tut. Es ist ärgerlich, nach der Ankündigung des großen Zweigs ein reines Theming in PHP machen zu müssen. Danke für Ihren Vorschlag.
Artfulrobot
Diese Funktion scheint nicht aufgerufen zu werden. Ich denke, das ist für "Link mit separaten Titel- und URL-Elementen". Das template_preprocess_linksDing wird auch nicht aufgerufen (das ist etwas Spezifisches, obwohl es generisch klingt).
Artfulrobot
Template Preprocess Links ist für Linklisten, soweit ich sehe, können Sie immer Twig Debug aktivieren, um zu sehen, welche Template / Preprocess-Funktion für die Ausgabe verwendet wurde
rémy
Ja, keines davon wird zum Formatieren von Links verwendet. Tatsächlich wird core/lib/Drupal/Core/Utility/LinkGenerator.phps generate()verwendet, und dies erzwingt die Weitergabe des Textes, Html::escape()sodass dies nicht möglich ist, ohne den Link-Formatierer von Drupal vollständig zu umgehen.
Artfulrobot
Sie können diesen Service wie jeden anderen überschreiben, siehe hier tim.millwoodonline.co.uk/post/125163259445/…
rémy
0

Versuchen Sie diesen Code:

{% if links -%}
  {%- if heading -%}
    {%- if heading.level -%}
  <{{ heading.level }}{{ heading.attributes }}>{{ heading.text }}</{{ heading.level }}>
{%- else -%}
  <h2{{ heading.attributes }}>{{ heading.text }}</h2>
   {%- endif -%}
  {%- endif -%}
  <ul{{ attributes }}>
{%- for item in links -%}
  <li{{ item.attributes }}>
        {%- if item.link -%}

    <!--{{ item.link }} this line must stay -->

    <a href="{{ item.link['#url'] }}"
      {{ item.attributes.addClass(classes) }}
      {{ item.attributes.setAttribute('title', item.text ) }}
      {{ item.attributes.setAttribute('lang', item.link['#options'].language.id ) }}
      {{ item.attributes.setAttribute('aria-label', item.text ) }}>
        <img alt="{{ item.link['#title'] }}" src="/themes/subtheme/img/flag_{{ item.link['#options'].language.id }}.jpg" class="align-center">
    </a>


    {%- elseif item.text_attributes -%}
      <span{{ item.text_attributes }}>{{ item.text }}</span>
    {%- else -%}
      {{ item.text }}
    {%- endif -%}
  </li>
{%- endfor -%}

{% - endif%}

oder dieses (es kommt von: https://github.com/liip/bund_drupal_starterkit_theme/blob/master/templates/navigation/links--language-block.html.twig ):

{% if links and links|length > 1 -%}
  <ul>
    {%- for item in links -%}
      <li>
        {%- if item.link -%}

      <!--{{ item.link }} to do: remove this line without breaking the urls -->

      {% if item.link['#options'].language.id == current_language %}
        {% set classes = ['active'] %}
      {% else %}
        {% set classes = [''] %}
      {% endif %}
      {% set url = path(item.link['#url'].routeName, item.link['#url'].routeParameters, item.link['#url'].options) %}

    {%- else -%}
      {% set classes = ['disabled'] %}
      {% set url = '#' %}
    {%- endif -%}

    <a href="{{ url }}"
      {{ item.attributes.addClass(classes) }}
      {{ item.attributes.setAttribute('title', item.text ) }}
      {{ item.attributes.setAttribute('lang', item.link['#options'].language.id ) }}
      {{ item.attributes.setAttribute('aria-label', item.text ) }}>
        {{ item.link['#options'].language.id | upper }}
    </a>
  </li>
{%- endfor -%}
  </ul>
{%- endif %}
Tomek
quelle