# Events & Callbacks

> React to consent changes with Zest events and callbacks

Source: https://zest.freshjuice.dev/docs/events/

## Overview

Zest provides three ways to react to consent changes:

1. **DOM Events** - Standard browser events you can listen to anywhere
2. **`Zest.on()` / `Zest.once()` subscribers** _(v2.0.0+)_ - Cleaner API with explicit unsubscribe
3. **Callbacks** - Functions defined in your configuration

## DOM Events

### zest:ready

Fired when Zest has initialized and determined the current consent state.

```javascript
document.addEventListener('zest:ready', (e) => {
  console.log('Zest initialized');
  console.log('Current consent:', e.detail.consent);
  // { essential: true, functional: false, analytics: false, marketing: false }
});
```

### zest:consent

Fired when the user accepts cookies (either all or selected categories).

```javascript
document.addEventListener('zest:consent', (e) => {
  console.log('User consented');
  console.log('Categories:', e.detail.consent);
  console.log('Previous state:', e.detail.previous);

  if (e.detail.consent.analytics) {
    initializeAnalytics();
  }

  if (e.detail.consent.marketing) {
    initializeMarketing();
  }
});
```

### zest:reject

Fired when the user rejects all non-essential cookies.

```javascript
document.addEventListener('zest:reject', (e) => {
  console.log('User rejected all');
  console.log('Consent state:', e.detail.consent);
  // { essential: true, functional: false, analytics: false, marketing: false }
});
```

### zest:change

Fired on any consent change (accept, reject, or modification).

```javascript
document.addEventListener('zest:change', (e) => {
  console.log('Consent changed');
  console.log('New state:', e.detail.consent);
  console.log('Previous state:', e.detail.previous);

  // Check what changed
  const prev = e.detail.previous;
  const curr = e.detail.consent;

  if (prev.analytics !== curr.analytics) {
    console.log('Analytics consent changed to:', curr.analytics);
  }
});
```

### zest:show

Fired when the banner or modal is displayed.

```javascript
document.addEventListener('zest:show', (e) => {
  console.log('UI shown:', e.detail.type); // 'banner' or 'modal'
});
```

### zest:hide

Fired when the banner or modal is hidden.

```javascript
document.addEventListener('zest:hide', (e) => {
  console.log('UI hidden:', e.detail.type); // 'banner' or 'modal'
});
```

## Subscriber API

_Since v2.0.0._

`Zest.on()` and `Zest.once()` are thin wrappers around the DOM events above that return an **unsubscribe function** and let you omit the `zest:` prefix. Handy when you want a clean teardown path (e.g. inside a SPA component).

### Zest.on(event, callback)

```javascript
const unsubscribe = Zest.on('consent', (consent) => {
  if (consent.analytics) initAnalytics();
});

// Later, stop listening
unsubscribe();
```

The event name accepts both forms:

```javascript
Zest.on('change', handler);         // short form
Zest.on('zest:change', handler);    // full event name
Zest.on(Zest.EVENTS.CHANGE, handler); // via constants
```

### Zest.once(event, callback)

Same signature, but the callback fires at most once and then auto-unsubscribes:

```javascript
Zest.once('ready', (consent) => {
  console.log('Zest is ready, current consent:', consent);
});
```

### Unsubscribing everywhere

Every subscriber — DOM event, `on`, `once`, callback — receives the same payload shape. Pick the style that fits your codebase; you don't have to be consistent.

```javascript
// In a React component, on/off cleanly
useEffect(() => {
  const unsubscribe = Zest.on('change', (consent) => {
    setConsent(consent);
  });
  return unsubscribe;
}, []);
```

## Configuration Callbacks

Define callbacks in your ZestConfig for a cleaner setup:

```javascript
window.ZestConfig = {
  callbacks: {
    onReady: (consent) => {
      console.log('Ready with consent:', consent);
    },

    onAccept: (consent) => {
      console.log('Accepted:', consent);

      if (consent.analytics) {
        gtag('config', 'GA-XXXXX');
      }
    },

    onReject: () => {
      console.log('All rejected');
    },

    onChange: (consent) => {
      console.log('Changed to:', consent);
    }
  }
};
```

### Callback Reference

| Callback | Parameters | When Fired |
|----------|------------|------------|
| `onReady` | `consent` | Zest initialized |
| `onAccept` | `consent` | User accepted (any categories) |
| `onReject` | none | User rejected all |
| `onChange` | `consent` | Any consent change |

## Practical Examples

### Google Analytics Integration

```javascript
document.addEventListener('zest:consent', (e) => {
  if (e.detail.consent.analytics) {
    // Load gtag.js dynamically
    const script = document.createElement('script');
    script.src = 'https://www.googletagmanager.com/gtag/js?id=GA-XXXXX';
    script.async = true;
    document.head.appendChild(script);

    script.onload = () => {
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', 'GA-XXXXX');
    };
  }
});
```

### Facebook Pixel Integration

```javascript
document.addEventListener('zest:consent', (e) => {
  if (e.detail.consent.marketing) {
    !function(f,b,e,v,n,t,s)
    {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
    n.callMethod.apply(n,arguments):n.queue.push(arguments)};
    if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
    n.queue=[];t=b.createElement(e);t.async=!0;
    t.src=v;s=b.getElementsByTagName(e)[0];
    s.parentNode.insertBefore(t,s)}(window, document,'script',
    'https://connect.facebook.net/en_US/fbevents.js');
    fbq('init', 'PIXEL_ID');
    fbq('track', 'PageView');
  }
});
```

### Consent-Aware Analytics Wrapper

```javascript
class ConsentAwareAnalytics {
  constructor() {
    this.queue = [];
    this.initialized = false;

    document.addEventListener('zest:consent', (e) => {
      if (e.detail.consent.analytics) {
        this.initialize();
      }
    });

    // Check if already consented
    if (typeof Zest !== 'undefined' && Zest.hasConsent('analytics')) {
      this.initialize();
    }
  }

  initialize() {
    if (this.initialized) return;
    this.initialized = true;

    // Initialize your analytics here
    // ...

    // Process queued events
    this.queue.forEach(event => this.track(event.name, event.data));
    this.queue = [];
  }

  track(name, data) {
    if (!this.initialized) {
      this.queue.push({ name, data });
      return;
    }
    // Send to analytics
    gtag('event', name, data);
  }
}

const analytics = new ConsentAwareAnalytics();

// Safe to call anytime - will queue if no consent
analytics.track('button_click', { label: 'signup' });
```

### Show Banner on Footer Link Click

```html
<footer>
  <a href="#" onclick="Zest.showSettings(); return false;">
    Cookie Preferences
  </a>
</footer>
```

### Log Consent for Compliance

```javascript
document.addEventListener('zest:consent', (e) => {
  const proof = Zest.getConsentProof();

  // Send to your server
  fetch('/api/consent-log', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      consent: proof,
      userAgent: navigator.userAgent,
      url: window.location.href
    })
  });
});
```

