There might be cases where you would like to make a component react to changes in another component, but TEI Publisher does not provide the necessary connection by default. Components in Publisher mainly communicate via signals sent into channels. You can thus always connect two components via custom code by listening to events sent by a first component and - when received - trigger another component.
Let’s assume you would like certain parts of your document to be collapsed initially. You are thus outputting a pb-collapse
. However, instead of preloading the collapsed content, it should be loaded only when the user actually wants to look at it and thus expands the pb-collapse
. We’re thus using a pb-view
(or pb-load
) for the content:
<pb-collapse emit="meta">
<div slot="collapse-trigger">About this text</div>
<pb-view slot="collapse-content" on-update src="intro" subscribe="meta"></pb-view>
</pb-collapse>
The pb-view
will load content from a different document (defined by a pb-document
with id intro). The @on-update
property instructs the view to not load content immediately, but only when it receives a pb-update event.
We thus need to wire the pb-collapse
with the pb-view
by sending a pb-update event to the view whenever the collapse is expanded. pb-collapse
will emit a pb-collapse-open event in this case. We can thus connect the pb-collapse-open signal with pb-update by inserting a few lines of custom code into the HTML template we use to display the text:
<script>
window.addEventListener('load', () => {
document.addEventListener('pb-collapse-open', (ev) => {
if (ev.detail && ev.detail.key && ev.detail.key === 'meta') {
document.dispatchEvent(new CustomEvent('pb-update', {
detail: {
key: 'meta'
}
}));
}
});
});
</script>
Important here is the key
property in the event details: within TEI Publisher, it always indicates the channel for which an event should apply. It corresponds to the channels we defined with @subscribe
and @emit
on the HTML elements above. If there’s no key, it means that the event is targetted at the global default channel. For simple cases, emitting events to the default channel might be enough, but if your HTML gets more complex, you want to be sure that this particular pb-collapse
is sending its events to the correct pb-view
- and not all of them.
Beginning with version 1.14.1 of pb-components (check your version), there’s a utility class which can be used to simplify the code above:
<script>
window.addEventListener('load', () =>
pbEvents.subscribe('pb-collapse-open', 'meta', () => pbEvents.emit('pb-update', 'meta'), true)
);
</script>
The arguments to subscribe
are:
By passing ‘true’ for the fourth argument, we prevent the pb-update
event being resent whenever the user expands the collapse again. Loading the content of the collapse once should be enough.
Alternatively you can use the subscribeOnce
function if you would like the event handler to only execute once. This function returns a Promise, which resolves when the event is received:
<script>
window.addEventListener('load', () =>
pbEvents.subscribeOnce('pb-collapse-open', 'meta')
.then(() => pbEvents.emit('pb-update', 'meta'))
);
</script>
The first two arguments for emit
are the same as for subscribe
. The third argument can be used to pass data to the event. The event handler can access this information in the ev.detail
property as in the following snippet:
<script>
pbEvents.subscribe('pb-update', ['transcription', 'translation'], (ev) => {
console.log(ev.detail); // should include foo: 'baz'
}, true);
pbEvents.emit('pb-update', 'translation', {
foo: 'baz'
});
</script>
You can find a demo of the code covered above in the pb-components documentation.
pb-view
Quite often you may want to inspect the HTML rendition of your TEI as displayed in a pb-view
, e.g. to add event listeners, extract images etc. Unfortunately, pb-view
renders the content into the so-called shadow DOM, i.e. a private section which is not visible to the world outside the pb-view
. The advantage of this approach is that anything inside the shadow will not interfere with the rest of the page, allowing us to display completely heterogenous documents on the same page. The disadvantage is that code outside the pb-view
can not “look into” the component.
Fortunately it is possible though to intercept the events pb-view
emits. Of particularily interest is the pb-update
event: this event is emitted by pb-view
whenever it has finished loading new content, and the event details include a reference to the actual content rendered by the component. You can access it as an HTML element in ev.detail.root
.
For example, let’s assume that our ODD transformation outputs an HTML element with class credits
. We don’t want to show this as part of the rendered text though, but instead display it prominently on the top of the page. This can be done as follows:
pbEvents.subscribe("pb-update", "transcription", (ev) => {
const credits = ev.detail.root.querySelector(".credits");
const creditsTarget = document.getElementById("credits");
if (credits && creditsTarget) {
creditsTarget.innerHTML = credits.innerHTML;
}
credits.style.display = 'none';
});
The code first tries to find an element with CSS class .credits
. If it exists, it’s content is copied into another element with id credits
contained somewhere else on the page. The original element is then hidden.