craftcms / cms

Build bespoke content experiences with Craft.
https://craftcms.com
Other
3.2k stars 617 forks source link

Link field type #15251

Closed brandonkelly closed 1 day ago

brandonkelly commented 1 week ago

Description

Adds a new “Link” field type, which replaces the former “URL” field type.

Link fields feature a simple UI for defining a link, of one of the allowed types the field was configured with:

Three Link fields. The first is configured with only the “URL” link type, and has a link to https://craftcms.com filled in. The second is configured with multiple link types, and has “URL” selected, set to https://console.craftcms.com. The third has “Entry” selected, and an entry titled “What’s on Tap” is selected.

Textual link types automatically set valid values to hyperlinks on blur, or when the Esc key is pressed. Hyperlinks are automatically replaced with text inputs when the input container is clicked on, or the “Edit” disclosure menu option is chosen.

Templating/GraphQL

Regardless of the link type selected, field values will return the full, rendered URL.

# GraphQL query
query {
  entry(id: 99) {
    ...on link_Entry {
      link1
      link2
      link3
    }
  }
}
// Response
{
  "data": {
    "entry": {
      "link1": "https://craftcms.com",
      "link2": "https://console.craftcms.com",
      "link3": "https://craft5.ddev.site/work"
    }
  }
}

The following properties are also available off of the value in Twig templates:

{% if entry.myLinkField.element %}
  {{ entry.myLinkField.link }}
{% else %}
  <a href="{{ entry.myLinkField }}" target="_blank" rel="noopener">
    {{ entry.myLinkField.label }}
  </a>
{% endif %}

Registering custom link types

Additional link types can be registered via the EVENT_REGISTER_LINK_TYPES event. Link types are defined as classes which extend one of the following base classes:

Here’s a how to register a link type for a custom element type:

// Plugin.php
use craft\events\RegisterComponentTypesEvent;
use craft\fields\Link;
use modules\foo\AssetLinkType;
use yii\base\Event;

Event::on(
    Link::class,
    Link::EVENT_REGISTER_LINK_TYPES,
    function(RegisterComponentTypesEvent $event
) {
    $event->types[] = MyElementLinkType::class;
});
// MyElementLinkType.php
use craft\fields\linktypes\BaseElementLinkType;

abstract class MyElementLinkType extends BaseElementLinkType
{
    protected static function elementType(): string
    {
        return MyElementType::class;
    }

    protected function availableSourceKeys(): array
    {
        return [
            // list the source keys that contain elements that have URLs
            // ...
        ];
    }

    protected function selectionCriteria(): array
    {
        return array_merge(parent::selectionCriteria(), [
            // ...
        ]);
    }
}

Notes

Related issues

linear[bot] commented 1 week ago

CMS-1279 Link field

mmikkel commented 1 week ago

This is awesome, thanks a lot Brandon.

Pretty much the only thing I'd hope to see added to this, is some way of accessing the actual element selected, in links using the BaseElementLinkType class.

While I think it's fine that Link fields doesn't expose an input for labels/link text, it'd be great (and would eliminate some AX friction) if it was possible to do something like this, in order to have element links' texts/labels default to the selected element's title:

{% if entry.myLinkField.type == 'element-entry' %}
  <a href="{{ entry.myLinkField }}">{{ entry.myLinkLabelField | default(entry.myLinkField.element.title) }}</a>
{% else %}
  <a href="{{ entry.myLinkField }}" target="_blank" rel="noopener">
    {{ entry.myLinkLabelField }}
  </a>
{% endif %}

Alternatively, instead of exposing the element for BaseElementLinkType via something like myLinkField.element, maybe the BaseLinkType class could implement something like a getText() method, that returns the value (i.e. URL) for the base class, the selected element's title for the BaseElementLinkType, and can be overridden to return whatever for custom link types.

BenParizek commented 1 week ago

So glad to finally see this and a way to register custom Elements Link Types.

Links started the internet! They will feel so at home in a CMS.

leevigraham commented 1 week ago

Link fields do not have the ability to show settings to control target, label, etc. Those can be captured via separate custom fields.

I assume you're thinking multiple links would be a matrix field with a block that contains the link field and the overrides.

brandonkelly commented 1 week ago

@leevigraham That or something like #14600.

brandonkelly commented 1 week ago

@mmikkel Both good ideas, so just added them both. Updated the PR description with:

The following properties are also available off of the value in Twig templates:

  • type – The link type’s ID (url, category, email, entry, or tel)
  • label – A suggested label that could be used in anchor links (e.g. the URL sans-schema, or the element’s title)
  • link – An anchor tag made up of the link URL and label
  • element – The linked element, if applicable
{% if entry.myLinkField.element %}
  {{ entry.myLinkField.link }}
{% else %}
  <a href="{{ entry.myLinkField }}" target="_blank" rel="noopener">
    {{ entry.myLinkField.label }}
  </a>
{% endif %}
MoritzLost commented 6 days ago

@brandonkelly Very good addition 🎉

One thing we have used a lot with Typed Link Field and now Hyper is the ability to append something (a fragment or query parameters) to entry links. For example, you want to link to a specific section on a specific entry, so you select the entry and enter the fragment for the section on that page. Or you want to link to a search page with pre-selected filters or a search term, in this case you would enter an additional query string. Would it be possible to support this for element links?

There's still a use-case for Hyper, which supports full field layouts and creating multiple links in one field. But appending something to entry URLs is much more common, so this would be nice to have as a core feature.

Mosnar commented 6 days ago

This is awesome and much appreciated! I don't see it replacing Hyper (yet?), but at the very least it will solve for a lot of basic scenarios. Thank you for listening to community feedback and getting out a workable solution instead of waiting for multi-element-type sources. ❤️

brandonkelly commented 6 days ago

@MoritzLost I don’t see that feature in Hyper. You must be capturing the fragment with a custom field?

MoritzLost commented 6 days ago

@MoritzLost I don’t see that feature in Hyper. You must be capturing the fragment with a custom field?

@brandonkelly No, it's a native attribute (urlSuffix) in Hyper (mentioned here in passing). It's not placed in the field layout by default for new fields, but can be added to the field layout for every link type:

hyper-link-suffix

In Typed Link Field it's an option for every link type, this results in an additional text input next to the URL / entry selector:

typed-link-field-url-suffix

brandonkelly commented 6 days ago

@MoritzLost Gotcha… still, that could pretty easily be added with an adjacent custom field, just like “Open in a new window” etc.

droemmelbert commented 6 days ago

@brandonkelly Would it also make sense to include linking to assets? At least that is a feature that I would be using quite a bit when a button links to a PDF file.

brandonkelly commented 6 days ago

@droemmelbert I decided not to initially because it seemed unlikely that there’s a need to link to assets or a webpage URL; more likely you’d just want a dedicated field just for linking to an asset, at which point you’d be better off using an Assets field, which would be much more specialized than Link (fine-tuned control over available sources, file types, permission enforcement, plus direct-to-field upload support).

If I’m wrong and there is a legitimate need for a field that links to both webpage URLs and assets, I can consider pulling those features in.

Mosnar commented 6 days ago

@droemmelbert I decided not to initially because it seemed unlikely that there’s a need to link to assets or a webpage URL; more likely you’d just want a dedicated field just for linking to an asset, at which point you’d be better off using an Assets field, which would be much more specialized than Link (fine-tuned control over available sources, file types, permission enforcement, plus direct-to-field upload support).

If I’m wrong and there is a legitimate need for a field that links to both webpage URLs and assets, I can consider pulling those features in.

I think it’s worth including. Consider a multi-purpose component such as a button in the hero or a CTA on a page. Sometimes those get used to link to forms, sometimes they get linked to downloadables, such as PDFs. I typically enable it as an option by default to prevent people from pasting a URL directly to an asset rather than asking a developer to change it.

mmikkel commented 6 days ago

I also think assets are definitely worth including. FWIW, clients link to assets in CKEditor fields all the time, e.g. to create links for downloading brand materials, etc.

MattWilcox commented 6 days ago

Honestly, the "link" field is almost always a "button" field, and yup... clients "link to" assets, so I'd be down for Assets as well.

brandonkelly commented 2 days ago

Asset link type option added 🎉

Required some refactoring—link types get instantiated now, and can define settings. All element type-based link types now show a “Sources” setting, allowing you to fine tune which sources should be available in element selection modals.

The asset link type also has “Allowed File Types”, “Show unpermitted volumes”, and “Show unpermitted files” settings, similar to Assets fields.

Asset link type settings

Uploading directly to the field isn’t supported, as that would add a huge amount of complexity and be a pretty big departure from other link type UIs. (It’s possible to upload within the asset selection modal though.)