Skip to content
This repository has been archived by the owner on Oct 31, 2024. It is now read-only.

Commit

Permalink
feat(composables): interceptor functionality for events across applic…
Browse files Browse the repository at this point in the history
…ation (#1103)
  • Loading branch information
patzick authored Sep 17, 2020
1 parent cef0917 commit b315782
Show file tree
Hide file tree
Showing 25 changed files with 622 additions and 4 deletions.
26 changes: 26 additions & 0 deletions api/composables.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
```ts

import { AddressType } from '@shopware-pwa/commons/interfaces/models/checkout/customer/CustomerAddress';
import { ApplicationVueContext as ApplicationVueContext_2 } from '@shopware-pwa/composables';
import { Association } from '@shopware-pwa/commons/interfaces/search/Association';
import { BillingAddress } from '@shopware-pwa/commons/interfaces/request/GuestOrderParams';
import { Cart } from '@shopware-pwa/commons/interfaces/models/checkout/cart/Cart';
Expand Down Expand Up @@ -43,6 +44,8 @@ export interface ApplicationVueContext extends VueConstructor {
// (undocumented)
$i18n?: any;
// (undocumented)
$interceptors?: any;
// (undocumented)
$router?: any;
// (undocumented)
$shopwareApiInstance?: ShopwareApiInstance;
Expand All @@ -55,6 +58,8 @@ export interface ApplicationVueContext extends VueConstructor {
// (undocumented)
i18n?: any;
// (undocumented)
interceptors?: any;
// (undocumented)
router?: any;
// (undocumented)
shopwareApiInstance?: ShopwareApiInstance;
Expand Down Expand Up @@ -109,6 +114,7 @@ export function getApplicationContext(rootContext: ApplicationVueContext, key?:
i18n: any;
cookies: any;
shopwareDefaults: any;
interceptors: any;
contextName: string;
};

Expand All @@ -120,13 +126,23 @@ export function getDefaultApiParams(): {
};
};

// @beta
export const INTERCEPTOR_KEYS: {
ADD_TO_CART: string;
ERROR: string;
};

// @beta
export interface IUseAddToCart {
addToCart: () => Promise<void>;
error: Ref<string>;
getStock: Ref<number | null>;
isInCart: Ref<boolean>;
loading: Ref<boolean>;
onAddToCart: (fn: (params: {
product: Product;
quantity: Number;
}) => void) => void;
quantity: Ref<number>;
}

Expand Down Expand Up @@ -189,6 +205,13 @@ export interface IUseCheckout {
updateGuestOrderParams: (params: Partial<GuestOrderParams>) => void;
}

// @beta
export interface IUseIntercept {
broadcast: (broadcastKey: string, value: any) => void;
disconnect: (broadcastKey: string, method: Function) => void;
intercept: (broadcastKey: string, method: Function) => void;
}

// @beta
export interface IUseNavigation {
// (undocumented)
Expand Down Expand Up @@ -337,6 +360,9 @@ export const useDefaults: (rootContext: ApplicationVueContext, defaultsKey: stri
getAssociationsConfig: () => Association[];
};

// @beta
export const useIntercept: (rootContext: ApplicationVueContext_2) => IUseIntercept;

// @beta
export const useNavigation: (rootContext: ApplicationVueContext) => IUseNavigation;

Expand Down
1 change: 1 addition & 0 deletions docs/.vuepress/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module.exports = {
"/landing/concepts/plugins",
"/landing/concepts/payment",
"/landing/concepts/snippets",
"/landing/concepts/interceptor",
],
},
{
Expand Down
150 changes: 150 additions & 0 deletions docs/landing/concepts/interceptor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Events interceptor <Badge text="since 0.4.0" type="info"/>

Sometimes there is a need to react on some event in the application. You may want to show a notification when product is added to cart, or send that event to your Analytics system.

To easily react to changes and events across the application we created `useIntercept` composable.
It allows you to safely broadcast and intercept system events, and add your own events.

:::tip Useful info

All system events can be found in `INTERCEPTOR_KEYS` constant, you'll find it by importing it from composables package `import { INTERCEPTOR_KEYS } from "@shopware-pwa/composables"`
:::

## Usage examples

### In any vue component

In most cases, you'll use this mechanism indirectly. Let's take for example `useAddToCart` composable.
It introduces a new type of methods `onXXX`, in our case it's `onAddToCart`

so when you'd like to react on that event:

```js
setup({ product }, { root }) {
const {
onAddToCart,
} = useAddToCart(root, product)

onAddToCart(({product, quantity}) => {
// here you can show notification, or send GTM event
})

return {
}
},
```

:::warning Important
Remember, that when you listen on the event in component, then you listen to it in every component instance. So if you have two active instances of this component and you want to show notification on adding to cart event, then you'd end up with two notifications. If you want to react on specific method do it in the component triggering the action or in Nuxt plugin (description below).
:::

```js
setup({ product }, { root }) {
const {
addToCart
} = useAddToCart(root, product)

const yourAddToCartWrapper = async () => {
await addToCart()
// here you could show notification
}

return {
yourAddToCartWrapper
}
},
```

### In Nuxt plugin

If you don't want to change any component in order to react on some event, you can create new Nuxt plugin, for example, `src/plugins/my-interceptors.js`, add it to `nuxt.config.js` file:

```
/*
** Plugins to load before mounting the App
** https://nuxtjs.org/guide/plugins
*/
plugins: ['~/plugins/my-interceptor.js'],
```

and then inside this file, you can use interceptors directly like this (it will be also useful for intercepting your own events)

```js
import {
useIntercept,
INTERCEPTOR_KEYS,
useNotifications,
} from "@shopware-pwa/composables";

export default async ({ app }) => {
const { intercept } = useIntercept(app);
const { pushSuccess } = useNotifications(app);
intercept(INTERCEPTOR_KEYS.ADD_TO_CART, ({ product }) => {
pushSuccess(
`${product?.translated?.name || product?.name} has been added to cart.`
);
});
};
```
or like this
```js
import {
useAddToCart
useNotifications,
} from "@shopware-pwa/composables";
export default async ({ app }) => {
const { onAddToCart } = useAddToCart(app);
const { pushSuccess } = useNotifications(app);
onAddToCart(({ product }) => {
pushSuccess(
`${product?.translated?.name || product?.name} has been added to cart.`
);
});
};
```
### Broadcast custom event
You can broadcast your custom events accross application. Your custom event will not be defined in `INTERCEPTOR_KEYS`, so you can broadcast your event like this:
```js
setup(props, { root }) {
const { broadcast } = useIntercept(root)
const yourMethod = async () => {
// ...some action
broadcast('my-custom-event', {param1: "xyz"})
}
return {
yourMethod
}
},
```
### Stop intercepting events
If the interceptor is used in component, it ends life with the life of this component, so you don't need to worry about potential memory leaks and invoking methods in dead components.
If you're invoking it in nuxt plugin it will listen for the whole time when the application is displayed.
You might want to stop listening on the event at some point though, for example, another event causes that you don't want to listen to the event anymore.
`disconnect` method from `useIntercept` comes with help. This is how you can do this:
```js
import { useIntercept, INTERCEPTOR_KEYS } from "@shopware-pwa/composables";
export default async ({ app }) => {
const { intercept, disconnect } = useIntercept(app);
const myOnAddToCartMethod = ({ product }) => {
// do something with event
};
intercept(INTERCEPTOR_KEYS.ADD_TO_CART, myOnAddToCartMethod);
intercept("my-custom-event", () => {
disconnect(INTERCEPTOR_KEYS.ADD_TO_CART, myOnAddToCartMethod);
});
};
```
2 changes: 2 additions & 0 deletions docs/landing/getting-started/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ So for example even if you overwritten SwButton as above you can still call them

**FEATURE**: introduced `useDefaults` composable, which is used inside other composables to provide specific fields from API. You can extend it in `shopware-pwa.config.js` file. Read how in [useDefaults docs](/landing/resources/api/composables.usedefaults.html)

**FEATURE**: we introduced Interceptors functionality to listen on events across application. More [here](/landing/concepts/interceptor.html)

## Migrate version 0.2.x to 0.3.x

All changes are documented in our [Changelog](https://github.com/DivanteLtd/shopware-pwa/blob/master/CHANGELOG.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@shopware-pwa/composables](./composables.md) &gt; [ApplicationVueContext](./composables.applicationvuecontext.md) &gt; [$interceptors](./composables.applicationvuecontext._interceptors.md)

## ApplicationVueContext.$interceptors property

> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.
>
<b>Signature:</b>

```typescript
$interceptors?: any;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@shopware-pwa/composables](./composables.md) &gt; [ApplicationVueContext](./composables.applicationvuecontext.md) &gt; [interceptors](./composables.applicationvuecontext.interceptors.md)

## ApplicationVueContext.interceptors property

> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.
>
<b>Signature:</b>

```typescript
interceptors?: any;
```
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ export interface ApplicationVueContext extends VueConstructor
| --- | --- | --- |
| [$cookies](./composables.applicationvuecontext._cookies.md) | any | <b><i>(BETA)</i></b> |
| [$i18n](./composables.applicationvuecontext._i18n.md) | any | <b><i>(BETA)</i></b> |
| [$interceptors](./composables.applicationvuecontext._interceptors.md) | any | <b><i>(BETA)</i></b> |
| [$router](./composables.applicationvuecontext._router.md) | any | <b><i>(BETA)</i></b> |
| [$shopwareApiInstance](./composables.applicationvuecontext._shopwareapiinstance.md) | ShopwareApiInstance | <b><i>(BETA)</i></b> |
| [$shopwareDefaults](./composables.applicationvuecontext._shopwaredefaults.md) | any | <b><i>(BETA)</i></b> |
| [$store](./composables.applicationvuecontext._store.md) | any | <b><i>(BETA)</i></b> |
| [cookies](./composables.applicationvuecontext.cookies.md) | any | <b><i>(BETA)</i></b> |
| [i18n](./composables.applicationvuecontext.i18n.md) | any | <b><i>(BETA)</i></b> |
| [interceptors](./composables.applicationvuecontext.interceptors.md) | any | <b><i>(BETA)</i></b> |
| [router](./composables.applicationvuecontext.router.md) | any | <b><i>(BETA)</i></b> |
| [shopwareApiInstance](./composables.applicationvuecontext.shopwareapiinstance.md) | ShopwareApiInstance | <b><i>(BETA)</i></b> |
| [shopwareDefaults](./composables.applicationvuecontext.shopwaredefaults.md) | any | <b><i>(BETA)</i></b> |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export declare function getApplicationContext(rootContext: ApplicationVueContext
i18n: any;
cookies: any;
shopwareDefaults: any;
interceptors: any;
contextName: string;
};
```
Expand All @@ -31,5 +32,5 @@ export declare function getApplicationContext(rootContext: ApplicationVueContext

<b>Returns:</b>

{ apiInstance: ShopwareApiInstance \| undefined; vuexStore: any; router: any; i18n: any; cookies: any; shopwareDefaults: any; contextName: string; }
{ apiInstance: ShopwareApiInstance \| undefined; vuexStore: any; router: any; i18n: any; cookies: any; shopwareDefaults: any; interceptors: any; contextName: string; }

19 changes: 19 additions & 0 deletions docs/landing/resources/api/composables.interceptor_keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@shopware-pwa/composables](./composables.md) &gt; [INTERCEPTOR\_KEYS](./composables.interceptor_keys.md)

## INTERCEPTOR\_KEYS variable

> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.
>
Keys used accross composables with the description of incommint parameters.

<b>Signature:</b>

```typescript
INTERCEPTOR_KEYS: {
ADD_TO_CART: string;
ERROR: string;
}
```
1 change: 1 addition & 0 deletions docs/landing/resources/api/composables.iuseaddtocart.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ export interface IUseAddToCart
| [getStock](./composables.iuseaddtocart.getstock.md) | Ref&lt;number \| null&gt; | <b><i>(BETA)</i></b> Returns product count in stock |
| [isInCart](./composables.iuseaddtocart.isincart.md) | Ref&lt;boolean&gt; | <b><i>(BETA)</i></b> Flag if product is already in cart |
| [loading](./composables.iuseaddtocart.loading.md) | Ref&lt;boolean&gt; | <b><i>(BETA)</i></b> Adding to cart is in progress |
| [onAddToCart](./composables.iuseaddtocart.onaddtocart.md) | (fn: (params: { product: Product; quantity: Number; }) =&gt; void) =&gt; void | <b><i>(BETA)</i></b> React on product added to cart |
| [quantity](./composables.iuseaddtocart.quantity.md) | Ref&lt;number&gt; | <b><i>(BETA)</i></b> If you want to add more that 1 product set quantity before invoking <code>addToCart</code> |

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@shopware-pwa/composables](./composables.md) &gt; [IUseAddToCart](./composables.iuseaddtocart.md) &gt; [onAddToCart](./composables.iuseaddtocart.onaddtocart.md)

## IUseAddToCart.onAddToCart property

> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.
>
React on product added to cart

<b>Signature:</b>

```typescript
onAddToCart: (fn: (params: {
product: Product;
quantity: Number;
}) => void) => void;
```
16 changes: 16 additions & 0 deletions docs/landing/resources/api/composables.iuseintercept.broadcast.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@shopware-pwa/composables](./composables.md) &gt; [IUseIntercept](./composables.iuseintercept.md) &gt; [broadcast](./composables.iuseintercept.broadcast.md)

## IUseIntercept.broadcast property

> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.
>
Broadcast new event

<b>Signature:</b>

```typescript
broadcast: (broadcastKey: string, value: any) => void;
```
16 changes: 16 additions & 0 deletions docs/landing/resources/api/composables.iuseintercept.disconnect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [@shopware-pwa/composables](./composables.md) &gt; [IUseIntercept](./composables.iuseintercept.md) &gt; [disconnect](./composables.iuseintercept.disconnect.md)

## IUseIntercept.disconnect property

> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.
>
Stop listening on event

<b>Signature:</b>

```typescript
disconnect: (broadcastKey: string, method: Function) => void;
```
Loading

1 comment on commit b315782

@vercel
Copy link

@vercel vercel bot commented on b315782 Sep 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.