diff --git a/components/x-live-blog-wrapper/readme.md b/components/x-live-blog-wrapper/readme.md index 28178d23d..6d70dcb88 100644 --- a/components/x-live-blog-wrapper/readme.md +++ b/components/x-live-blog-wrapper/readme.md @@ -2,7 +2,6 @@ This module displays a list of live blog posts using `x-live-blog-post` component. It also connects to an event stream which provides updates for the list. Based on these update events this component will add, remove and update `x-live-blog-post` components in the list. - ## Installation This module is supported on Node 12 and is distributed on npm. @@ -15,23 +14,36 @@ The [`x-engine`][engine] module is used to inject your chosen runtime into the c [engine]: https://github.com/Financial-Times/x-dash/tree/HEAD/packages/x-engine - ## Usage The components provided by this module are all functions that expect a map of [properties](#properties). They can be used with vanilla JavaScript or JSX (If you are not familiar check out [WTF is JSX][jsx-wtf] first). Also worth noting, this component handles visibility tracking when passed `postTrackerConfig` property. The `postTrackerConfig` property contains the follow fields: + - onEntersViewport: Callback function with an event parameter - OnRead: Callback function with an event parameter - OnError: Callback function with an event parameter - usePostTracker: Boolean. When set to `true` LiveBlogWrapper component creates and manages an instance of PostTracker and reports read, view, error events. -All fields are required to use post tracking. + All fields are required to use post tracking. - - For example if you were writing your application using React you could use the component like this: +For example if you were writing your application using React you could use the component like this: ```jsx import React from 'react'; import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper'; +// example props. Not exhaustive but shows use of tracking +const props = { + posts: [], + id: 'live-blog-wrapper', + articleUrl: `https://www.ft.com/content/${id}`, + showShareButtons: true, + postTrackerConfig: { + onEntersViewport: (event) => console.log(event, 'view'), + onRead: (event) => console.log(event, 'read'), + onError: (event) => console.log(event, 'error'), + usePostTracker: true, + } +} + // A == B == C const a = LiveBlogWrapper(props); const b = ; @@ -45,47 +57,49 @@ All `x-` components are designed to be compatible with a variety of runtimes, no The `x-live-blog-wrapper` component also exports `PostTracker`, a class used to track post visibility. It reports a read and a view event for individual posts in the `LiveBlogWrapper` component. If you choose to handle post tracking yourself, this class should be used as an alternative. ```js -import { PostTracker } from '@financial-times/x-live-blog-wrapper'; +import { PostTracker } from '@financial-times/x-live-blog-wrapper' -const onEntersViewPort = (event) => {} // Enrich event with app context and report to tracking medium. -const onRead = (event) => {} // Enrich event with app context and report to tracking medium. +const onEntersViewPort = (event) => {} // Enrich event with app context and report to tracking medium. +const onRead = (event) => {} // Enrich event with app context and report to tracking medium. const onError = (event) => {} // Enrich event with app context and report to tracking medium. -const liveBlogPackageId = '00000-00000-00000-00000' +const liveBlogPackageId = '00000-00000-00000-00000' /** * @type {import('@financial-times/x-live-blog-wrapper').PostTracker.PostTrackerConfig} */ let config = { - query: 'article[data-trackable="live-post"]', // required - minMillisecondsToReport: 5000, - returnVisibleElement: true, - observerUpdateEventString: 'LiveBlogWrapper.INSERT_POST', // required - liveBlogWrapperQuery: `div[data-live-blog-wrapper-id="${liveBlogPackageId}"]`, // required, where id = liveblogpackage.id - liveBlogWrapper: this.props.liveBlogWrapperElementRef - ? this.props.liveBlogWrapperElementRef.current - : undefined, - onEntersViewport: (event) => callbacks.onEntersViewport(event), // required - onRead: (event) => callbacks.onRead(event), // required - onError: (event) => callbacks.onError(event) // required + query: 'article[data-trackable="live-post"]', // required + minMillisecondsToReport: 5000, + returnVisibleElement: true, + observerUpdateEventString: 'LiveBlogWrapper.INSERT_POST', // required + liveBlogWrapperQuery: `div[data-live-blog-wrapper-id="${liveBlogPackageId}"]`, // required, where id = liveblogpackage.id + liveBlogWrapper: this.props.liveBlogWrapperElementRef + ? this.props.liveBlogWrapperElementRef.current + : undefined, + onEntersViewport: (event) => onEntersViewport(event), // required + onRead: (event) => onRead(event), // required + onError: (event) => onError(event) // required } -new PostTracker(config); - +const tracker = new PostTracker(config) ``` ### Client side rendering + This component can be used at the client side. ```jsx -import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper'; - - +import { LiveBlogWrapper } from '@financial-times/x-live-blog-wrapper' + +; ``` ### Server side rendering and hydrating + When rendering this component at the server side, hydration data must be rendered to the document using `Serialiser` and `HydrationData` components which are provided by `x-interaction`. To successfully hydrate this component at the client side, the `id` property **must** be provided when rendering it at the server side. `x-interaction` will add this id to the markup as a `data-x-dash-id` attribute. This property can later be used to identify the markup. @@ -109,9 +123,9 @@ const serialiser = new Serialiser(); To hydrate this component at the client side, use `hydrate()` function provided by `x-interaction`. ```js -import { hydrate } from '@financial-times/x-interaction'; +import { hydrate } from '@financial-times/x-interaction' -hydrate(); +hydrate() ``` ### Inserting posts on the client side @@ -119,6 +133,7 @@ hydrate(); When live updates come in you can insert a new post by dispatching an action to the component's wrapper. Client side: + ```js import { hydrate } from '@financial-times/x-interaction'; @@ -149,14 +164,17 @@ wrapperElement.dispatchEvent( ``` ### Client side events + This component dispatches the following client side events to notify the consuming app about live updates. Consuming apps typically use these events to initialise Origami components on the newly rendered markup. ```jsx - + ``` ```js @@ -178,13 +196,12 @@ wrapperElement.addEventListener('LiveBlogWrapper.INSERT_POST', ### Properties -Feature | Type | Notes ------------------|--------|---------------------------- -`articleUrl` | String | URL of the live blog - used for sharing -`showShareButtons` | Boolean | if `true` displays social media sharing buttons in posts -`posts` | Array | Array of live blog post data -`id` | String | **(required)** Unique id used for identifying the element in the document. - +| Feature | Type | Notes | +| ------------------ | ------- | -------------------------------------------------------------------------- | +| `articleUrl` | String | URL of the live blog - used for sharing | +| `showShareButtons` | Boolean | if `true` displays social media sharing buttons in posts | +| `posts` | Array | Array of live blog post data | +| `id` | String | **(required)** Unique id used for identifying the element in the document. | ## Configuring the `next-live-event-api` endpoint URL. @@ -192,6 +209,6 @@ If you want to configure the URL for `next-live-event-api`, add the following pl ```javascript new webpack.DefinePlugin({ - LIVE_EVENT_API_URL: JSON.stringify('http://localhost:3003') + LIVE_EVENT_API_URL: JSON.stringify('http://localhost:3003') }) -``` \ No newline at end of file +``` diff --git a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx index 75d93dfa0..e09a89e7d 100644 --- a/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx +++ b/components/x-live-blog-wrapper/src/LiveBlogWrapper.jsx @@ -45,8 +45,14 @@ class BaseLiveBlogWrapper extends Component { super(props) } - componentDidMount() { - this.setUp() + componentDidUpdate(prevProps) { + if (this.props.posts && this.props.posts.length && !this.state.tracker) { + this.setUp() + } + + if (this.state.tracker && prevProps.posts.length !== this.props.length) { + this.state.tracker.observeNewElements() + } } componentWillUnmount() { diff --git a/components/x-live-blog-wrapper/src/lib/post-tracker.js b/components/x-live-blog-wrapper/src/lib/post-tracker.js index 188846b22..043d26840 100644 --- a/components/x-live-blog-wrapper/src/lib/post-tracker.js +++ b/components/x-live-blog-wrapper/src/lib/post-tracker.js @@ -244,6 +244,13 @@ export class PostTracker { } } + /** + * Observes new post rendered in the wrapper + */ + observeNewElements() { + this.observeElements(this.config.query) + } + /** * Disconnects the intersection observer and stops watching for visibility changes * @returns