As you know, a page consists of different layers including canvas, text and annotations. Links are part of the annotation layer. The `Viewer`
component renders links as usual `a`
elements.
Clicking them brings users to the associated website. This example demonstrates how we can prevent that default behavior. Instead of taking users to the target website directly,
we will display a confirmation modal asking if users really want to the target website. This kind of user experience is used in a lot of websites nowadays.
The idea of implementation is quite straightforward. We can do it in three steps:
- Find all link elements in the annotation layer
- Handle their
`click`
events
- Show a modal when users click a link
Find all link elements
The steps mentioned above will be managed by creating a custom plugin, `openLinksPlugin`
for example. It is possible for a plugin to listen to the event when the annotation layer is rendered completely.
import type { Plugin, PluginOnAnnotationLayerRender } from '@react-pdf-viewer/core';
const openLinksPlugin = (): Plugin => {
const findLinks = (e: PluginOnAnnotationLayerRender) => {
};
return {
onAnnotationLayerRender: findLinks,
};
};
The `PluginOnAnnotationLayerRender`
type consists of the following properties
Property | Type | Description |
---|
`annotations` | `PdfJs.Annotation[]` | List of annotations in the page |
`container` | `HTMLElement` | The container element that contains annotation elements |
`pageIndex` | `number` | The current page number |
`rotation` | `number` | The current rotation number |
`scale` | `number` | The current scale number |
Each link annotation is an `a`
element with the `data-target="external"`
attribute. So we can query all link elements in the page easily:
const findLinks = (e: PluginOnAnnotationLayerRender) => {
e.container.querySelectorAll('a[data-target="external"]').forEach((link) => {
link.addEventListener('click', handleClick);
});
};
Don't worry about the `handleClick`
handler. We will talk about it in the next section.
Handle the `click`
event
Like many built-in
plugins, the internal states of a plugin are usually managed by a store. In our example, we would like to know which link is clicked by users:
interface StoreProps {
clickedTarget?: string;
}
Let's create a store instance in the plugin entry point:
import * as React from 'react';
import { createStore } from '@react-pdf-viewer/core';
const openLinksPlugin = (): Plugin => {
const store = React.useMemo(() => createStore<StoreProps>({}), []);
};
Whenever a link is clicked, its target will be saved to our store. It can be done by using the store's `update`
function:
const handleClick = (e: Event) => {
e.preventDefault();
const href = (e.target as HTMLLinkElement).href;
store.update('clickedTarget', href);
};
`e.preventDefault()`
avoids the default behavior of clicking links. It prevents the browsers from redirecting to the target website.
Show the confirmation modal
We start this step by creating the `ConfirmationModal`
component that manages the clicked link as an internal state. The store gives us the ability of synchronizing its states with a component states via the `subscribe`
function:
import type { Store } from '@react-pdf-viewer/core';
const ConfirmationModal: React.FC<{
store: Store<StoreProps>;
}> = ({ store }) => {
const [target, setTarget] = React.useState('');
const handleTargetClicked = (clickedTarget: string) => {
setTarget(clickedTarget);
};
React.useEffect(() => {
store.subscribe('clickedTarget', handleTargetClicked);
return () => {
store.unsubscribe('clickedTarget', handleTargetClicked);
};
}, []);
};
It's worth noting that we also use the store's `unsubscribe`
function to stop the synchronization when the component is destroyed.
It's very easy to display a modal when the `target`
state is defined:
import { Modal } from '@react-pdf-viewer/core';
const ConfirmationModal: React.FC<{
store: Store<StoreProps>;
}> = ({ store }) => {
return (
target && <Modal isOpened={true} closeOnClickOutside={false} closeOnEscape={false} content={renderContent} />
);
};
It's up to you to use any components that display a modal. The sample code above uses the `Modal`
component provided by the `@react-pdf-viewer/core`
package.
The `renderContent`
function shows the content of the modal which has at least two buttons. The first button is to cancel the confirmation. The second one is to confirm and will bring users to the target website.
import { PrimaryButton, MinimalButton } from '@react-pdf-viewer/core';
const ConfirmationModal: React.FC<{
store: Store<StoreProps>;
}> = ({ store }) => {
const handleCancel = () => {
setTarget('');
};
const handleConfirm = () => {
setTarget('');
window.open(target, '_blank');
};
const renderContent = () => (
<div style={{ marginRight: '0.25rem' }}>
<MinimalButton onClick={handleCancel}>No</MinimalButton>
</div>
<PrimaryButton onClick={handleConfirm}>Yes</PrimaryButton>
);
};
The final part of our journey is how to attach the modal to the `Viewer`
component. The plugin allows to do that via the `renderViewer`
property:
import type { RenderViewer, Slot } from '@react-pdf-viewer/core';
const openLinksPlugin = (): Plugin => {
const renderViewer = (renderViewerProps: RenderViewer): Slot => {
const currentSlot = renderViewerProps.slot;
if (currentSlot.subSlot) {
currentSlot.subSlot.children = (
<>
<ConfirmationModal store={store} />
{currentSlot.subSlot.children}
</>
);
}
return currentSlot;
};
return {
renderViewer,
};
};
Here is the live demo:
See also