From 936c5d0b592b62a3d1045619d3ca0a4a5a3a3595 Mon Sep 17 00:00:00 2001 From: Drew Powers Date: Mon, 22 Apr 2019 14:07:29 -0600 Subject: [PATCH] Propose event standard (#87) * Update events, add tests * Fix docs modal --- .eslintignore | 1 + __mocks__/@stencil/state-tunnel/index.ts | 85 +++++++++ src/components.d.ts | 9 +- src/components/manifold-marketplace/readme.md | 49 +++--- .../manifold-plan-details.spec.ts | 50 +++++- .../manifold-plan-details.tsx | 82 +++++---- .../manifold-plan-details/readme.md | 9 +- .../manifold-plan-selector/readme.md | 52 +++--- .../ manifold-service-card.spec.ts | 20 +++ .../manifold-service-card.tsx | 20 ++- .../manifold-service-card/readme.md | 2 +- .../manifold-template-card.tsx | 13 +- .../manifold-template-card.tsx/readme.md | 6 +- src/index.html | 163 ++++++++++++------ 14 files changed, 383 insertions(+), 178 deletions(-) create mode 100644 __mocks__/@stencil/state-tunnel/index.ts create mode 100644 src/components/manifold-service-card/ manifold-service-card.spec.ts diff --git a/.eslintignore b/.eslintignore index d843e378b..a7ac8f542 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ +__mocks__ dist/ www/ diff --git a/__mocks__/@stencil/state-tunnel/index.ts b/__mocks__/@stencil/state-tunnel/index.ts new file mode 100644 index 000000000..9a2e01b9d --- /dev/null +++ b/__mocks__/@stencil/state-tunnel/index.ts @@ -0,0 +1,85 @@ +import { FunctionalComponent, HTMLStencilElement } from '@stencil/core'; +import { SubscribeCallback, ConsumerRenderer, PropList } from '../declarations'; + +export const createProviderConsumer = ( + defaultState: T, + consumerRender: ConsumerRenderer +) => { + let listeners: Map> = new Map(); + let currentState: T = defaultState; + + const updateListener = (fields: PropList, listener: HTMLStencilElement) => { + if (Array.isArray(fields)) { + [...fields].forEach(fieldName => { + (listener as any)[fieldName] = currentState[fieldName]; + }); + } else { + (listener as any)[fields] = { + ...(currentState as object), + } as T; + } + listener.forceUpdate(); + }; + + const subscribe: SubscribeCallback = (el: HTMLStencilElement, propList: PropList) => { + if (listeners.has(el)) { + return () => {}; + } + listeners.set(el, propList); + updateListener(propList, el); + + return () => { + listeners.delete(el); + }; + }; + + const Provider: FunctionalComponent<{ state: T }> = ({ state }, children) => { + currentState = state; + listeners.forEach(updateListener); + return children; + }; + + const Consumer: FunctionalComponent<{}> = (props, children) => { + // The casting on subscribe is to allow for crossover through the stencil compiler + // In the future we should allow for generics in components. + return consumerRender(subscribe, children[0] as any); + }; + + const injectProps = (childComponent: any, fieldList: PropList) => { + let unsubscribe: any = null; + + const elementRefName = Object.keys(childComponent.properties).find(propName => { + return childComponent.properties[propName].elementRef == true; + }); + if (elementRefName == undefined) { + throw new Error( + `Please ensure that your Component ${ + childComponent.is + } has an attribute with an "@Element" decorator. ` + + `This is required to be able to inject properties.` + ); + } + + const prevComponentWillLoad = childComponent.prototype.componentWillLoad; + childComponent.prototype.componentWillLoad = function() { + unsubscribe = subscribe(this[elementRefName], fieldList); + if (prevComponentWillLoad) { + return prevComponentWillLoad.bind(this)(); + } + }; + + const prevComponentDidUnload = childComponent.prototype.componentDidUnload; + childComponent.prototype.componentDidUnload = function() { + unsubscribe(); + if (prevComponentDidUnload) { + return prevComponentDidUnload.bind(this)(); + } + }; + }; + + return { + Provider, + Consumer, + injectProps, + }; +}; diff --git a/src/components.d.ts b/src/components.d.ts index 482fd9bee..e76165cf3 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -323,8 +323,9 @@ export namespace Components { 'hideCta'?: boolean; 'isExistingResource'?: boolean; 'linkFormat'?: string; - 'onManifold-planCTA-click'?: (event: CustomEvent) => void; - 'onManifold-planUpdated'?: (event: CustomEvent) => void; + 'onManifold-planSelector-change'?: (event: CustomEvent) => void; + 'onManifold-planSelector-click'?: (event: CustomEvent) => void; + 'onManifold-planSelector-load'?: (event: CustomEvent) => void; 'plan'?: Catalog.ExpandedPlan; 'product'?: Catalog.Product; } @@ -475,7 +476,7 @@ export namespace Components { 'linkFormat'?: string; 'logo'?: string; 'name'?: string; - 'onManifold-serviceCard-click'?: (event: CustomEvent) => void; + 'onManifold-marketplace-click'?: (event: CustomEvent) => void; 'productId'?: string; } @@ -486,7 +487,7 @@ export namespace Components { interface ManifoldTemplateCardAttributes extends StencilHTMLAttributes { 'category'?: string; 'linkFormat'?: string; - 'onManifold-templateCard-click'?: (event: CustomEvent) => void; + 'onManifold-template-click'?: (event: CustomEvent) => void; } interface ManifoldToast { diff --git a/src/components/manifold-marketplace/readme.md b/src/components/manifold-marketplace/readme.md index 142d740e5..bd1aaee1c 100644 --- a/src/components/manifold-marketplace/readme.md +++ b/src/components/manifold-marketplace/readme.md @@ -60,47 +60,38 @@ comma-separated list: ``` -## Navigation +## Events -When users click on a product card, you expect something to happen, right? By -default, service cards will emit a `manifold-serviceCard-click` custom event -whenever a user clicks anywhere on a card. You can listen for it like so, -and use this value to navigate client-side or perform some other action of -your choice: +This component emits [custom +events](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent) +when it updates. To listen to those events, add an event listener either on +the component itself, or `document`. ```js -document.addEventListener('manifold-serviceCard-click', { detail: { label } } => { - alert(`You clicked the card for ${label}`); +document.addEventListener('manifold-marketplace-click', { detail: { productLabel } } => { + alert(`You clicked the card for ${productLabel}`); }); ``` -Alternately, if you’d like the service cards to be plain, ol’ `` tags, you -can specify a `link-format` attribute, where `:product` will be substituted -with each product’s URL-friendly slug: +The following events are emitted: + +| Event Name | Description | Data | +| :--------------------------- | :------------------------------------------------------ | :-------------------------- | +| `manifold-marketplace-click` | Fires whenever a user has clicked on a product. | `productId`, `productLabel` | +| `manifold-template-click` | Fires whenever a user has clicked on a custom template. | `category` | + +## Navigation + +By default, service cards will only emit the `manifold-marketplace-click` +event (above). But it can also be turned into an `` tag by specifying +`link-format`: ```html ``` -Note that template cards also emit an event as well: -`manifold-templateCard-click`. - -#### Handling Events in React - -Attaching listeners to custom components in React [requires the use of refs](https://custom-elements-everywhere.com/). Example: - -```js -marketplaceLoaded(node) { - node.addEventListener("manifold-serviceCard-click", ({ detail: { label } }) => { - alert(`You clicked the card for ${label}`); - }); -} - -render() { - return ; -} -``` +`:product` will be replaced with the url-friendly slug for the product. diff --git a/src/components/manifold-plan-details/manifold-plan-details.spec.ts b/src/components/manifold-plan-details/manifold-plan-details.spec.ts index 5c872fc2f..42391ab2e 100644 --- a/src/components/manifold-plan-details/manifold-plan-details.spec.ts +++ b/src/components/manifold-plan-details/manifold-plan-details.spec.ts @@ -24,20 +24,60 @@ describe(``, () => { }); }); - it('dispatches update event when loaded', () => { + it('dispatches load event', () => { const planDetails = new PlanDetails(); planDetails.plan = ExpandedPlanCustom; planDetails.product = Product; const mock = { emit: jest.fn() }; - planDetails.planUpdated = mock; + planDetails.planLoad = mock; planDetails.componentWillLoad(); expect(mock.emit).toHaveBeenCalledWith({ features: { instance_class: 'db.t2.micro', redundancy: false, storage: 5 }, - id: '235exy25wvzpxj52p87bh87gbnj4y', - label: 'custom', - product: 'jawsdb-mysql', + planId: '235exy25wvzpxj52p87bh87gbnj4y', + planLabel: 'custom', + productLabel: 'jawsdb-mysql', + }); + }); + + it('dispatches update event', () => { + const planDetails = new PlanDetails(); + planDetails.plan = ExpandedPlanCustom; + planDetails.product = Product; + planDetails.planLoad = { emit: jest.fn() }; + planDetails.componentWillLoad(); // Set initial features + + const mock = { emit: jest.fn() }; + planDetails.planUpdate = mock; + + // Set redundancy: true + const e = new CustomEvent('', { detail: { name: 'redundancy', value: true } }); + planDetails.handleChangeValue(e); + expect(mock.emit).toHaveBeenCalledWith({ + features: { redundancy: true, instance_class: 'db.t2.micro', storage: 5 }, + planId: '235exy25wvzpxj52p87bh87gbnj4y', + planLabel: 'custom', + productLabel: 'jawsdb-mysql', + }); + }); + + it('dispatches click event', () => { + const planDetails = new PlanDetails(); + planDetails.plan = ExpandedPlanCustom; + planDetails.product = Product; + planDetails.planLoad = { emit: jest.fn() }; + planDetails.componentWillLoad(); // Set initial features + + const mock = { emit: jest.fn() }; + planDetails.planClick = mock; + + planDetails.onClick(new Event('click')); + expect(mock.emit).toHaveBeenCalledWith({ + features: { instance_class: 'db.t2.micro', redundancy: false, storage: 5 }, + planId: '235exy25wvzpxj52p87bh87gbnj4y', + planLabel: 'custom', + productLabel: 'jawsdb-mysql', }); }); }); diff --git a/src/components/manifold-plan-details/manifold-plan-details.tsx b/src/components/manifold-plan-details/manifold-plan-details.tsx index a91c7b19a..009221679 100644 --- a/src/components/manifold-plan-details/manifold-plan-details.tsx +++ b/src/components/manifold-plan-details/manifold-plan-details.tsx @@ -4,6 +4,13 @@ import { initialFeatures } from '../../utils/plan'; import { FeatureValue } from './components/FeatureValue'; import { FeatureLabel } from './components/FeatureLabel'; +interface EventDetail { + planId: string; + planLabel: string; + productLabel: string | undefined; + features: UserFeatures; +} + @Component({ tag: 'manifold-plan-details', styleUrl: 'plan-details.css', @@ -11,37 +18,54 @@ import { FeatureLabel } from './components/FeatureLabel'; }) export class ManifoldPlanDetails { @Prop() isExistingResource?: boolean; - @Prop() plan?: Catalog.ExpandedPlan; + @Prop() hideCta?: boolean = false; @Prop() linkFormat?: string; + @Prop() plan?: Catalog.ExpandedPlan; @Prop() product?: Catalog.Product; - @Prop() hideCta?: boolean = false; @State() features: UserFeatures = {}; - @Event({ - eventName: 'manifold-planUpdated', - bubbles: true, - }) - planUpdated: EventEmitter; + @Event({ eventName: 'manifold-planSelector-change', bubbles: true }) planUpdate: EventEmitter; + @Event({ eventName: 'manifold-planSelector-click', bubbles: true }) planClick: EventEmitter; + @Event({ eventName: 'manifold-planSelector-load', bubbles: true }) planLoad: EventEmitter; @Watch('plan') onUpdate(newPlan: Catalog.ExpandedPlan) { const features = this.initialFeatures(newPlan); this.features = features; // If plan changed, we want to reset all user-selected values - this.updatedPlanHandler({ features }); // Dispatch change event when plan changed + const detail: EventDetail = { + planId: newPlan.id, + planLabel: newPlan.body.label, + productLabel: this.product && this.product.body.label, + features, + }; + this.planUpdate.emit(detail); } - @Event({ - eventName: 'manifold-planCTA-click', - bubbles: true, - }) - ctaClicked: EventEmitter; componentWillLoad() { const features = this.initialFeatures(); this.features = features; // Set default features the first time - this.updatedPlanHandler({ features }); // Dispatch change event when loaded + if (this.plan && this.product) { + // This conditional should always fire on component load + const detail: EventDetail = { + planId: this.plan.id, + planLabel: this.plan.body.label, + productLabel: this.product.body.label, + features, + }; + this.planLoad.emit(detail); + } } handleChangeValue({ detail: { name, value } }: CustomEvent) { const features = { ...this.features, [name]: value }; - this.features = features; - this.updatedPlanHandler({ features }); // Dispatch change event when user changed feature + this.features = features; // User-selected features + if (this.plan && this.product) { + // Same as above: this should always fire; just needed for TS + const detail: EventDetail = { + planId: this.plan.id, + planLabel: this.plan.body.label, + productLabel: this.product.body.label, + features, + }; + this.planUpdate.emit(detail); + } } initialFeatures(plan: Catalog.ExpandedPlan | undefined = this.plan): UserFeatures { @@ -49,20 +73,6 @@ export class ManifoldPlanDetails { return { ...initialFeatures(plan.body.expanded_features) }; } - updatedPlanHandler({ - id = this.plan && this.plan.id, - label = this.plan && this.plan.body.label, - product = this.product && this.product.body.label, - features = this.features, - }) { - this.planUpdated.emit({ - id, - label, - product, - features, - }); - } - get ctaLink() { if (!this.product || !this.plan) return undefined; if (typeof this.linkFormat !== 'string') return undefined; @@ -144,11 +154,15 @@ export class ManifoldPlanDetails { } onClick = (e: Event): void => { - if (!this.linkFormat) { + if (!this.linkFormat && this.plan && this.product) { e.preventDefault(); - const product = this.product && this.product.body.label; - const plan = this.plan && this.plan.body.label; - this.ctaClicked.emit({ product, plan, features: this.features }); + const detail: EventDetail = { + productLabel: this.product.body.label, + planId: this.plan.id, + planLabel: this.plan.body.label, + features: this.features, + }; + this.planClick.emit(detail); } }; diff --git a/src/components/manifold-plan-details/readme.md b/src/components/manifold-plan-details/readme.md index ba7fc0d3c..28758819e 100644 --- a/src/components/manifold-plan-details/readme.md +++ b/src/components/manifold-plan-details/readme.md @@ -18,10 +18,11 @@ ## Events -| Event | Description | Type | -| ------------------------ | ----------- | ------------------- | -| `manifold-planCTA-click` | | `CustomEvent` | -| `manifold-planUpdated` | | `CustomEvent` | +| Event | Description | Type | +| ------------------------------ | ----------- | ------------------- | +| `manifold-planSelector-change` | | `CustomEvent` | +| `manifold-planSelector-click` | | `CustomEvent` | +| `manifold-planSelector-load` | | `CustomEvent` | ---------------------------------------------- diff --git a/src/components/manifold-plan-selector/readme.md b/src/components/manifold-plan-selector/readme.md index b28822801..af752409a 100644 --- a/src/components/manifold-plan-selector/readme.md +++ b/src/components/manifold-plan-selector/readme.md @@ -6,44 +6,36 @@ Display the plans for a product. ``` -## Product Label +You can find the `:product` label for each at +`https://manifold.co/services/:product`. -You can find the `:product` label for each at `https://manifold.co/services/:product`. +## Events -## Detecting changes - -Events are dispatched on the `manifold-planUpdated` custom event. To listen -for that, listen for the event on `document` like so: +This component emits [custom +events](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent) +when it updates. To listen to those events, add an event listener either on +the component itself, or `document`. ```js -document.addEventListener('manifold-planUpdated', ({ detail }) => { +document.addEventListener('manifold-planSelector-change', ({ detail }) => { console.log(detail); }); -// { id: "2357v8j36f5h866c32ddwwjxvfe8j", label: "nvidia-1080ti-100gb-ssd", product: "zerosix", features: { … } } } +// { planId: "2357v8j36f5h866c32ddwwjxvfe8j", planLabel: "nvidia-1080ti-100gb-ssd", productLabel: "zerosix", features: { … } } } ``` -## Navigation +The following events are emitted: -The large CTA in the bottom-right -is configurable. By default, this component emits a -`manifold-planCTA-click` custom event whenever the main CTA is clicked. -Listen for it like so: +| Event Name | Description | Data | +| :----------------------------- | :------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------ | +| `manifold-planSelector-change` | Fires whenever a user makes a change. | `planID`, `planLabel`, `productLabel`, `features` | +| `manifold-planSelector-load` | Identical to `-update` above, but this fires once on DOM mount to set the initial state (i.e. user hasn’t interacted yet). | `planID`, `planLabel`, `productLabel`, `features` | +| `manifold-planSelector-click` | If the CTA is showing (see `hide-cta` below), this will fire when clicked. | `planID`, `planLabel`, `productLabel`, `features` | -```js -document.addEventListener( - 'manifold-productCTA-click', - ({ detail: { product, plan, features } }) => { - alert( - `You clicked the CTA for the ${plan} plan on ${product} with these features: ${JSON.stringify( - features - )}` - ); - } -); -``` +## Navigation -To turn the CTA into an `` tag, specify a `link-format` attribute, using -`:product`, `:plan`, and `:features` as placeholders: +By default, the CTA bottom-right will fire the `manifold-planSelector-click` +event (above). But it can also be turned into an `` tag by specifying +`link-format`: ```html ` tag, specify a `link-format` attribute, using ``` -### Hiding provision button +`:plan`, `:product`, and `:features` (for customizable plans) will all be +replaced with url-friendly slugs for each. In most cases, these are all +passable to [**data components**](#data-components). + +### Hiding CTA If you would like to hide the CTA altogether, specify `hide-cta`: diff --git a/src/components/manifold-service-card/ manifold-service-card.spec.ts b/src/components/manifold-service-card/ manifold-service-card.spec.ts new file mode 100644 index 000000000..852411ad9 --- /dev/null +++ b/src/components/manifold-service-card/ manifold-service-card.spec.ts @@ -0,0 +1,20 @@ +import { ManifoldServiceCard } from './manifold-service-card'; +import { Product } from '../../spec/mock/catalog'; + +it('dispatches click event', () => { + const serviceCard = new ManifoldServiceCard(); + serviceCard.label = Product.body.label; + serviceCard.productId = Product.id; + serviceCard.logo = Product.body.logo_url; + serviceCard.name = Product.body.name; + serviceCard.description = Product.body.tagline; + + const mock = { emit: jest.fn() }; + serviceCard.marketplaceClick = mock; + + serviceCard.onClick(new Event('click')); + expect(mock.emit).toHaveBeenCalledWith({ + productId: Product.id, + productLabel: Product.body.label, + }); +}); diff --git a/src/components/manifold-service-card/manifold-service-card.tsx b/src/components/manifold-service-card/manifold-service-card.tsx index 52a27f723..fe7370fc4 100644 --- a/src/components/manifold-service-card/manifold-service-card.tsx +++ b/src/components/manifold-service-card/manifold-service-card.tsx @@ -4,6 +4,11 @@ import Tunnel from '../../data/connection'; import { withAuth } from '../../utils/auth'; import { Connection, connections } from '../../utils/connections'; +interface EventDetail { + productId?: string; + productLabel?: string; +} + @Component({ tag: 'manifold-service-card', styleUrl: 'manifold-service-card.css', @@ -11,11 +16,6 @@ import { Connection, connections } from '../../utils/connections'; }) export class ManifoldServiceCard { @Element() el: HTMLElement; - @Event({ - eventName: 'manifold-serviceCard-click', - bubbles: true, - }) - cardClicked: EventEmitter; @Prop() name?: string; @Prop() connection: Connection = connections.prod; @Prop() description?: string; @@ -25,7 +25,9 @@ export class ManifoldServiceCard { @Prop() productId?: string; @Prop() linkFormat?: string; @State() isFree: boolean = false; - @Watch('productId') watchHandler(newProductId: string) { + @Event({ eventName: 'manifold-marketplace-click', bubbles: true }) marketplaceClick: EventEmitter; + @Watch('productId') + watchHandler(newProductId: string) { this.fetchIsFree(newProductId); } @@ -53,7 +55,11 @@ export class ManifoldServiceCard { onClick = (e: Event): void => { if (!this.linkFormat) { e.preventDefault(); - this.cardClicked.emit({ label: this.label }); + const detail: EventDetail = { + productId: this.productId, + productLabel: this.label, + }; + this.marketplaceClick.emit(detail); } }; diff --git a/src/components/manifold-service-card/readme.md b/src/components/manifold-service-card/readme.md index 7af76a3d9..a979a17d6 100644 --- a/src/components/manifold-service-card/readme.md +++ b/src/components/manifold-service-card/readme.md @@ -23,7 +23,7 @@ Clickable service cards. | Event | Description | Type | | ---------------------------- | ----------- | ------------------- | -| `manifold-serviceCard-click` | | `CustomEvent` | +| `manifold-marketplace-click` | | `CustomEvent` | ---------------------------------------------- diff --git a/src/components/manifold-template-card.tsx/manifold-template-card.tsx b/src/components/manifold-template-card.tsx/manifold-template-card.tsx index 37669a0a3..585aa65b8 100644 --- a/src/components/manifold-template-card.tsx/manifold-template-card.tsx +++ b/src/components/manifold-template-card.tsx/manifold-template-card.tsx @@ -2,17 +2,17 @@ import { Component, Prop, Event, EventEmitter } from '@stencil/core'; import { categoryIcon } from '../../utils/marketplace'; import serviceTemplates from '../../data/templates'; +interface EventDetail { + category: string; +} + @Component({ tag: 'manifold-template-card', styleUrl: 'manifold-template-card.css', shadow: true, }) export class ManifoldTemplateCard { - @Event({ - eventName: 'manifold-templateCard-click', - bubbles: true, - }) - cardClicked: EventEmitter; + @Event({ eventName: 'manifold-template-click', bubbles: true }) templateClick: EventEmitter; @Prop() category: string; @Prop() linkFormat?: string; @@ -24,7 +24,8 @@ export class ManifoldTemplateCard { onClick = (e: Event): void => { if (!this.linkFormat) { e.preventDefault(); - this.cardClicked.emit({ label: this.category }); + const detail: EventDetail = { category: this.category }; + this.templateClick.emit(detail); } }; diff --git a/src/components/manifold-template-card.tsx/readme.md b/src/components/manifold-template-card.tsx/readme.md index 4e1009b72..20bac24e8 100644 --- a/src/components/manifold-template-card.tsx/readme.md +++ b/src/components/manifold-template-card.tsx/readme.md @@ -15,9 +15,9 @@ ## Events -| Event | Description | Type | -| ----------------------------- | ----------- | ------------------- | -| `manifold-templateCard-click` | | `CustomEvent` | +| Event | Description | Type | +| ------------------------- | ----------- | ------------------- | +| `manifold-template-click` | | `CustomEvent` | ---------------------------------------------- diff --git a/src/index.html b/src/index.html index 2916553a1..94ef0f5b8 100644 --- a/src/index.html +++ b/src/index.html @@ -318,46 +318,59 @@

Featuring products

<manifold-marketplace featured="piio,zerosix" />
 
- +

Events

- When users click on a product card, you expect something to happen, right? By - default, service cards will emit a manifold-serviceCard-click custom - event whenever a user clicks anywhere on a card. You can listen for it like so, and - use this value to navigate client-side or perform some other action of your choice: + This component emits + custom events + when it updates. To listen to those events, add an event listener either on the + component itself, or document.

-
document.addEventListener('manifold-serviceCard-click', { detail: { label } } => {
-  alert(`You clicked the card for ${label}`);
+              
document.addEventListener('manifold-marketplace-click', { detail: { productLabel } } => {
+  alert(`You clicked the card for ${productLabel}`);
 });
 
+

The following events are emitted:

+ + + + + + + + + + + + + + + + + + + + +
Event NameDescriptionData
manifold-marketplace-click + Fires whenever a user has clicked on a product. + + productId, productLabel +
manifold-template-click + Fires whenever a user has clicked on a custom template. + category
+

- Alternately, if you’d like the service cards to be plain, ol’ - <a> tags, you can specify a link-format attribute, - where :product will be substituted with each product’s URL-friendly - slug: + By default, service cards will only emit the + manifold-marketplace-click event (above). But it can also be turned + into an <a> tag by specifying link-format:

<manifold-marketplace link-format="/product/:product" />
 <!-- <a href="/product/jawsdb-mysql"> -->
 

- Note that template cards also emit an event as well: - manifold-templateCard-click. -

-

Handling Events in React

-

- Attaching listeners to custom components in React - requires the use of refs. - Example: + :product will be replaced with the url-friendly slug for the product.

-
marketplaceLoaded(node) {
-  node.addEventListener("manifold-serviceCard-click", ({ detail: { label } }) => {
-    alert(`You clicked the card for ${label}`);
-  });
-}
-
-render() {
-  return <manifold-marketplace ref={this.marketplaceLoaded} />;
-}
-

Properties

@@ -539,42 +552,71 @@

Plan Selector

Display the plans for a product.

<manifold-plan-selector product-label="jawsdb-mysql" />
 
-

Product Label

You can find the :product label for each at https://manifold.co/services/:product.

-

Detecting changes

+

Events

- Events are dispatched on the manifold-planUpdated custom event. To - listen for that, listen for the event on document like so: + This component emits + custom events + when it updates. To listen to those events, add an event listener either on the + component itself, or document.

-
document.addEventListener('manifold-planUpdated', ({ detail }) => {
+              
document.addEventListener('manifold-planSelector-change', ({ detail }) => {
   console.log(detail);
 });
-// { id: "2357v8j36f5h866c32ddwwjxvfe8j", label: "nvidia-1080ti-100gb-ssd", product: "zerosix", features: { … } } }
+// { planId: "2357v8j36f5h866c32ddwwjxvfe8j", planLabel: "nvidia-1080ti-100gb-ssd", productLabel: "zerosix", features: { … } } }
 
+

The following events are emitted:

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
Event NameDescriptionData
manifold-planSelector-changeFires whenever a user makes a change. + planID, planLabel, productLabel, + features +
manifold-planSelector-load + Identical to -update above, but this fires once on DOM mount to + set the initial state (i.e. user hasn’t interacted yet). + + planID, planLabel, productLabel, + features +
manifold-planSelector-click + If the CTA is showing (see hide-cta below), this will fire when + clicked. + + planID, planLabel, productLabel, + features +

- The large CTA in the bottom-right is configurable. By default, this component emits - a manifold-planCTA-click custom event whenever the main CTA is clicked. - Listen for it like so: -

-
document.addEventListener(
-  'manifold-productCTA-click',
-  ({ detail: { product, plan, features } }) => {
-    alert(
-      `You clicked the CTA for the ${plan} plan on ${product} with these features: ${JSON.stringify(
-        features
-      )}`
-    );
-  }
-);
-
-

- To turn the CTA into an <a> tag, specify a - link-format attribute, using :product, :plan, - and :features as placeholders: + By default, the CTA bottom-right will fire the + manifold-planSelector-click event (above). But it can also be turned + into an <a> tag by specifying link-format:

<manifold-product
   product-label="aiven-redis"
@@ -582,7 +624,14 @@ 
 />
 <!-- <a href="/product/aiven-redis?plan=startup-4&cpus=1"> -->
 
-

Hiding provision button

+

+ :plan, :product, and :features (for + customizable plans) will all be replaced with url-friendly slugs for each. In most + cases, these are all passable to + data components. +

+

Hiding CTA

If you would like to hide the CTA altogether, specify hide-cta:

<manifold-product product-label="till" hide-cta />
 
@@ -900,14 +949,14 @@

Example

document .querySelector('manifold-marketplace') - .addEventListener('manifold-serviceCard-click', e => { + .addEventListener('manifold-marketplace-click', e => { const overlay = document.createElement('div'); overlay.className = 'overlay'; const modal = document.createElement('div'); modal.className = 'modal'; overlay.appendChild(modal); const productPage = document.createElement('manifold-product'); - productPage.productLabel = e.detail.label; + productPage.productLabel = e.detail.productLabel; modal.appendChild(productPage); document.body.appendChild(overlay);