Hyperlinks
Hyperlinks are a concept to be able to link to virtually everything inside of Mayd.
Hyperlinks are a generic concept, that allows projects (and bundles) to add additional things to link to and have a consistent user interface.
Storing a Hyperlink
Storage of a hyperlink in your entity is done with the Hyperlink
embeddable:
class MyEntity
{
/**
* @ORM\Embedded(class="Mayd\Foundation\Entity\Hyperlink")
*/
private Hyperlink $link;
/**
*/
public function __construct ()
{
$this->link = new Hyperlink();
}
/**
*/
public function getLink () : Hyperlink
{
return $this->link;
}
}
Rendering a Hyperlink
You should always render hyperlinks using the hyperlink_tag()
twig function.
If in very rare cases you only need the URL, you can also use the hyperlink_url()
twig function.
You should always use the hyperlink_tag()
function, as it ensures that not only hyperlinks work
that generate an URL, but instead hyperlinks that only write HTML attributes can work as well.
These will just be broken if you only use hyperlink_url()
.
You can render them in PHP as well, use HyperlinkRenderer::renderAsTag()
.
Adding Custom Hyperlink Handlers
Mayd itself provides hyperlink handlers to link to pages, files and plain text URLs. But projects and bundles can create custom link targets.
To add a link target, you need to implement two things:
- The PHP hyperlink resolver, that resolves the data to an URL (or HTML attributes)
- The JS component that renders the input field
In the following example, we will implement a handler that lets you select an interactive component. The only interactive component we will implement is a modal containing the contact form. The idea is that everywhere, where we have a hyperlink, we want to be able to open the contact form modal.
We start with the PHP resolver:
Hyperlink Resolver
Your hyperlink resolver needs to implement the HyperlinkResolverInterface
. Please see the
comments in the interface on a more thorough explanation on what these methods do.
use Mayd\Foundation\Hyperlink\HyperlinkResolverInterface;
class InteractiveComponentHyperlinkResolver implements HyperlinkResolverInterface
{
/**
* @inheritDoc
*/
public static function getTypeKey () : string
{
return "interactive-component";
}
/**
* @inheritDoc
*/
public function resolve (array $data) : ?string
{
// This method receives the stored data and must
// return the full URL it resolves to.
//
// In our case we will never build a URL, so let's
// just always return `null`.
return null;
}
/**
* @inheritDoc
*/
public function normalize (array $data) : ?array
{
// This method receives the stored data and must
// prepare the data for the preview component
// (that we will build later).
//
// In our case there is nothing to prepare, so
// we can return it unmodified.
return $data;
}
}
We will never return a URL, because we don’t want to just link somewhere, but instead we want to trigger
a JS that opens our contact modal.
For that we need to additionally implement the HyperlinkAttributesResolverInterface
to be able
to render additional HTML attributes to the link/button, that will then be picked up by our frontend JS:
use Mayd\Foundation\Hyperlink\HyperlinkAttributesResolverInterface;
class InteractiveComponentHyperlinkResolver implements HyperlinkResolverInterface,
HyperlinkAttributesResolverInterface
{
// ...
/**
* @inheritDoc
*/
public function resolveAttributes (array $data) : array
{
$component = $data["component"] ?? null;
// first we need to validate that the stored data can
// be resolved properly
if (!\is_string($component) || "" === $component)
{
return [];
}
// then we return the additional attributes
return [
"data-component" => $component,
];
}
}
Now our hyperlink resolver is finished.
As a next step, we need to implement our form field:
Hyperlink Input Field
Our component must implement the BeyondWerkstoff.HyperlinkTypeHandler
interface.
For clarity’s sake here is the complete code with explaining comments. The actual implementation
of the select element was removed, as this is a basic Auslese
integration, that isn’t specific
for this example:
// first define our internal data structure
export interface InteractiveComponentHyperlinkHandlerData
{
component: string;
}
export class InteractiveComponentHyperlinkHandler implements BeyondWerkstoff.HyperlinkTypeHandler<InteractiveComponentHyperlinkHandlerData, InteractiveComponentHyperlinkHandlerData>
{
/**
* @inheritDoc
*/
public buildPreview (previewData: InteractiveComponentHyperlinkHandlerData): BeyondWerkstoff.CollapsedFormWidgetDetails | null
{
// this method receives the normalized data (from the PHP hyperlink resolver above)
// and must render a preview for it
const translator = app.get<Translator>("translator");
if (!previewData.component)
{
return null;
}
return {
title: translator.trans("..."),
// (please note that this `url` field is coming from `CollapsedFormWidgetDetails`
// it's not a url per se, but just in the preview in a monospaced preview field.)
url: translator.trans("..."),
};
}
/**
* @inheritDoc
*/
public renderForm (
data: InteractiveComponentHyperlinkHandlerData | undefined,
onUpdate: (
data: InteractiveComponentHyperlinkHandlerData,
previewData: (InteractiveComponentHyperlinkHandlerData | null)
) => void,
context: BeyondWerkstoff.HyperlinkFormTypeContext
): preact.ComponentChild
{
// this method receives the current data and is supposed to
// render an input to select the value and call `onUpdate()`
// with the new value.
//
// For example, we render a select here with "contact" as only entry.
// The "contact" key means "open the contact form". In the future we might
// want to expand this list by other interactive components.
// You should probably use the `FormRow` helper, to have a proper layout
return (
<FormRow
label="..."
errors={data ? this.validate(data) : []}
>
<Auslese ... />
</FormRow>
);
}
/**
* @inheritDoc
*/
public validate (data: InteractiveComponentHyperlinkHandlerData): string[]
{
// Validates the given data and returns a list of translation keys
// for validation errors.
// An empty array means that the value is valid.
if ("" === data.component)
{
return ["hyperlink.interactive-component.empty"]
}
return [];
}
}
As the final step for the backend JS integration, we need to register our hyperlink handler in the Mayd container.
Go to your project’s Mayd JS bundle and register it in configure()
:
class ProjectBackendBundle extends MaydBundle
{
/**
* @inheritDoc
*/
public configure (): void
{
this.get<HyperlinkTypeRegistry>("hyperlink.handlers").register(
// the key you defined in the PHP resolver above
"interactive-component",
new InteractiveComponentHyperlinkHandler()
);
}
}
Now we have full support for our new hyperlink target.
Frontend Integration
If you are building a regular hyperlink, that cleanly resolves to a single URL, you are done here.
In this example however we build an interactive component without a link target, so we still need to build the frontend integration.
Our hyperlink will resolve to buttons with a data-component="contact"
HTML attribute.
So we just mount our “open contact form component” on this button:
mount(
`button[data-component="contact"]`,
initContactModalForm
);
Note that hyperlink_tag()
will always render to a <button>
if there is no resolved URL.