Closed lihbr closed 3 years ago
This looks great and really opens up a lot of possibilities! Hot reloading preview updates and exits is a killer feature.
I have some thoughts below on the API and how we might alter the implementation.
Supporting both data-
redirects and window.prismic.middleware
functions feels a bit messy in my opinion. If the middleware functions can do whatever the data-
redirects can do, I think we should only have the more flexible option. The data-
attribute doesn't save much typing effort:
Example 1: Using middleware to redirect on a preview update.
<script
async
defer
src="https://static.cdn.prismic.io/prismic.min.js?repo=200629-sms-hoy&new=true"
></script>
<script>
window.prismic.middleware.previewUpdate = () => {
window.location = '/api/prismic-preview-update'
}
</script>
Compared to the data-
attribute method:
Example 2: Using data-redirect-url-on-update
to redirect on a preview update.
<script
async
defer
src="https://static.cdn.prismic.io/prismic.min.js?repo=200629-sms-hoy&new=true"
data-redirect-url-on-update="/api/prismic-preview-update"
></script>
Example 1 is longer, but it is clearer what it is doing. It is also extensible if necessary, where Example 2 is not immediately extensible. Example 2 could still implement a middleware function, but now functionality is split between two APIs.
In other words, Example 1 is clearer, more obviously extensible, and should be simpler to maintain for both consumers of and developers working on the toolbar.
(I recognize that I posted a long message on proposing the data-
attributes, but that was before we discussed the middleware functions in more depth π
)
window.prismic.middleware
sounds vague. It's not clear what window.prismic
is for, and as a result, it's not clear what the middleware
object is acting on.
window.prismic
is an existing API from a previous version of the toolbar (and maybe for other uses?), so that should not change. But we can still make it clearer what the middleware property is designed for. This allows window.prismic
to be extended to other use cases in the future, such as middleware for a different package.
window.prismic.toolbar = {
onPreviewUpdate: (next) => {
// Called on update events
},
onPreviewExit: (next) => {
// Called on exit events
},
}
In addition to renaming middleware
to toolbar
, we can also rename the properties to be more event-like. previewUpdate
could be named onPreviewUpdate
to imply that the function is called "on preview updates."
Event handlers are typically handled using addEventListener
for events like click
and blur
.
element.addEventListener('click', () => {
// Called on click events
})
Should we use this existing concept rather than build our own event dispatcher and handler?
Note: @lihbr originally proposed something very similar here: https://github.com/prismicio/prismic-toolbar/issues/68
Why should we use it?
Using the native event system allows us to hook into the existing event framework. It means we get support for multiple listeners, default behavior management, and listener cancellation for free.
Why shouldn't we use it?
It could introduce education complexity as there are more concepts to address. This can be mitigated by simply ignoring the advanced use cases and relying on users to understand native browser event management to make use of it. We can teach the simple, common use cases.
How would we use it?
Custom events can be registered using new Event(eventName)
. With this, arbitrary event types can be created, triggered, and reacted upon.
// In the toolbar, create a new event. CustomEvent is an Event with custom associated data.
const event = new CustomEvent('prismicPreviewUpdate', {
detail: 'arbitrary data can be provided if needed',
})
// Dispatch the event when an update happens:
window.dispatchEvent(event)
In users' code, event listeners can be managed like any other event listener:
const onPrismicPreviewUpdate = (event) => {
// Called on preview update events
}
const alsoOnPrismicPreviewUpdate = (event) => {
// Called on preview update events
}
// Add listeners:
window.addEventListener('prismicPreviewUpdate', onPrismicPreviewUpdate)
window.addEventListener('prismicPreviewUpdate', alsoOnPrismicPreviewUpdate)
// Use native event functions to remove a listener:
window.removeEventListener('prismicPreviewUpdate', onPrismicPreviewUpdate)
This gives the user flexibility in adding their own event listeners, but also enables framework integrations to add their own.
In the case that we want to support default behavior, such as the existing full page refresh, we can rely on event.preventDefault
to determine if the default behavior should be performed.
window.addEventListener('prismicPreviewUpdate', (event) => {
event.preventDefault()
// Do things without the full page refresh
})
In the toolbar's code, we can detect if event.preventDefault
was called via the return value of window.dispatchEvent
. Any event listener can cancel the default behavior.
// In the toolbar, register a new event.
const event = new CustomEvent('prismicPreviewUpdate')
// Dispatch the event when an update happens:
const shouldPerformDefaultBehavior = window.dispatchEvent(event)
if (shouldPerformDefaultBehavior) {
window.location.reload()
}
Thanks @angeloashmore!
I agree, will remove the data-*
attribute way of doing things.
Works for me if we don't decide to go the native events way.
That's the implementation I prefer too. If we find consensus with @arnaudlewis on it, I'll refactor the code to use the native event API!
I refactored the code to implement the simpler API and naming proposed by @angeloashmore. The initial PR post has been updated accordingly.
The last topic remaining to be answered is whether or not we should migrate to native events, apart from that one the PR is ready.
Update: code needs to be refactored to use native events we agreed on using during our last open-source meeting.
Refactored the code to use native custom events. Updated the initial post to reflect those API changes.
β The PR is ready, awaiting review & QA.
Looks nice and clean. Should the existing "init" event use the same framework you have setup with toolbarEvents
and dispatchToolbarEvent
?
Was kinda afraid to break something haha, but yes, I'll add it!
Looks nice and clean. Should the existing "init" event use the same framework you have setup with
toolbarEvents
anddispatchToolbarEvent
?
Updated.
Status
β Following first, second, and third implementations (described below), this PR is ready, awaiting review & QA.
Types of changes
Overview
This Pull Request adds events to the toolbar in the form of native JavaScript
CustomEvent
.The toolbar registers and triggers two custom events on the
window
object:prismicPreviewUpdate
andprismicPreviewEnd
. Users can listen to them using the native JavaScript API:window.addEventListener()
These events are meant to solve preview issues some frameworks have while also allowing some others to fully enable their preview capabilities.
Background Information
The toolbar is the companion integration of Prismic within Prismic-powered websites, it is mainly responsible for orchestrating Prismic preview sessions within those. In order to work for all Prismic users, the toolbar is designed to be and remain framework-agnostic.
This design choice appeared to cause issues for some frameworks that require specific actions to be performed for their preview capabilities to work correctly. These issues have been discussed lengthily within the following threads:
An initial proposal and integration were made last year to address these issues but did not lead to a merge for various reasons:
Description
Toolbar events allow users to listen to them on the
window
object. Registered event handlers are executed sequentially before the default toolbar behavior (hard reloading the page).Due to their relative complexity, custom events are not meant to be used by users directly. They are meant to be used by framework kits we provide to enable on users' behalf the best preview experience.
Usage
Each toolbar events follow the native JavaScript event API:
Toolbar events are cancelable: if any of the registered handlers calls
event.preventDefault()
, then the default behavior of the toolbar won't be executed.Some toolbar events come with additional data, users can access them inside the
event.detail
object.prismicPreviewUpdate
The preview update event will be fired every time there's an update on the preview session. The
event.detail
object will contain the new preview API ref (event.detail.ref
).Logging something each time the preview session gets updated:
Using Nuxt.js built-in refresh to allow hot reload instead of the default toolbar hard reload each time the preview session gets updated:
prismicPreviewEnd
The preview end event will be fired when the preview session ends (gets closed). The
event.detail
object will benull
.Logging something each time the preview session ends:
Exiting Next.js preview correctly each time the preview session ends:
Concerns
While I was able to test the toolbar locally with success, I wasn't able to test its iframe changes because I'm not able to run wroom locally.
I had to make the following changes to the iframe because the toolbar can now perform hot reload and cannot solely rely on a hard refresh to reset its state anymore: https://github.com/prismicio/prismic-toolbar/pull/89/files#diff-c58e75bd72479a6f9867399b1bb8bd6c1dbceba4eee441a890fb0d39eae9d33d
These changes allow the current preview state and its ref to be updated when the preview ping call returns a new ref, theoretically allowing the toolbar to perform multiple updates without hard reloading the page.
Without these changes, the toolbar wants to constantly update the preview as soon as a first change is detected.
This will need to be tested on wroom or with someone able to run wroom locally.
History
First implementation (window & redirect middleware)
# Overview This Pull Request adds two types of middleware to the toolbar: - _Window middleware_: they are functions registered in the `window` object that the toolbar can call upon certain events; - _Redirect middleware_: they are `data-*` attribute values registered on the toolbar script tag itself that the toolbar uses to redirect users upon certain events if present. Those two types of middleware are both available and fired for the toolbar `previewUpdate` and `previewEnd` events. They are meant to solve preview issues some frameworks have while also allowing some others to fully enable their preview capabilities. # Background Information The toolbar is the companion integration of Prismic within Prismic-powered websites, it is mainly responsible for orchestrating Prismic preview sessions within those. In order to work for all Prismic users, the toolbar is designed to be and remain framework-agnostic. This design choice appeared to cause issues for some frameworks that require specific actions to be performed for their preview capabilities to work correctly. These issues have been discussed lengthily within the following threads: - https://github.com/prismicio/prismic-toolbar/issues/84 - https://github.com/prismicio/issue-tracker-wroom/issues/290 (internal) An initial proposal and integration were made last year to address these issues but did not lead to a merge for various reasons: - https://github.com/prismicio/prismic-toolbar/issues/68 - https://github.com/prismicio/prismic-toolbar/pull/69 # Description ## Window Middleware Window middleware allow registering functions in the `window` object inside the already existing `window.prismic` namespace. If any of those functions are registered the toolbar will then run them and _may_ still execute its default behavior depending on those middleware functions implementations. Due to their _relative_ complexity, window middleware are not meant to be used by users directly. They are meant to be used by framework kits we provide to enable on users' behalf the best preview experience. ### Usage Each middleware function has the following interface: ```typescript type windowMiddlewareFunction = (next: () => void | Promise
Second implementation (window middleware)
# Overview This Pull Request adds window middleware to the toolbar in the form of events that can be registered. Window middleware are functions registered on the `window` object that the toolbar can call upon certain events. They are available and fired for the toolbar `previewUpdate` and `previewEnd` events. Window middleware are meant to solve preview issues some frameworks have while also allowing some others to fully enable their preview capabilities. # Background Information The toolbar is the companion integration of Prismic within Prismic-powered websites, it is mainly responsible for orchestrating Prismic preview sessions within those. In order to work for all Prismic users, the toolbar is designed to be and remain framework-agnostic. This design choice appeared to cause issues for some frameworks that require specific actions to be performed for their preview capabilities to work correctly. These issues have been discussed lengthily within the following threads: - https://github.com/prismicio/prismic-toolbar/issues/84 - https://github.com/prismicio/issue-tracker-wroom/issues/290 (internal) An initial proposal and integration were made last year to address these issues but did not lead to a merge for various reasons: - https://github.com/prismicio/prismic-toolbar/issues/68 - https://github.com/prismicio/prismic-toolbar/pull/69 # Description Window middleware allow registering functions on the `window` object inside the already existing `window.prismic` namespace. If any of those functions are registered the toolbar will then run them and _may_ still execute its default behavior depending on those middleware functions implementations. Due to their _relative_ complexity, window middleware are not meant to be used by users directly. They are meant to be used by framework kits we provide to enable on users' behalf the best preview experience. ## Usage Each middleware function has the following interface: ```typescript type windowMiddlewareFunction = (next: () => void | Promise