Free20 minutesintermediate

Custom Framework Integration

Integrate Zenovay with any JavaScript framework - vanilla JS, Svelte, Angular, and custom implementations.

integrationjavascriptcustomvanilla-jssvelte
Last updated: January 15, 2025

Integrate Zenovay analytics into any JavaScript framework or vanilla JavaScript application using our flexible tracking API.

Universal Script Installation

Basic Script Tag

Works with any framework:

<script
  defer
  data-tracking-code="YOUR_TRACKING_CODE"
  src="https://api.zenovay.com/z.js"
></script>

Script Attributes

AttributeDescription
data-tracking-codeRequired. Your website tracking code
deferRecommended. Load script without blocking page render

Standard Configuration

<script
  defer
  data-tracking-code="YOUR_TRACKING_CODE"
  src="https://api.zenovay.com/z.js"
></script>

Vanilla JavaScript

Basic Usage

// Track an event
window.zenovay('track', 'button_click', {
  button_id: 'signup',
  location: 'hero'
});

// Track page view (if auto-track disabled)
window.zenovay('page');

// Identify a user
window.zenovay('identify', 'user-123', {
  email: 'user@example.com',
  plan: 'pro'
});

// Track a goal
window.zenovay('goal', 'purchase', {
  value: 99.99
});

// Track revenue
window.zenovay('revenue', 149.99, 'USD', {
  order_id: 'ORD-001'
});

Wait for Script Load

function waitForZenovay(callback, maxWait = 5000) {
  const startTime = Date.now();

  function check() {
    if (window.zenovay) {
      callback(window.zenovay);
    } else if (Date.now() - startTime < maxWait) {
      requestAnimationFrame(check);
    }
  }

  check();
}

// Usage
waitForZenovay((zenovay) => {
  zenovay('track', 'app_ready');
});

Event Queue Pattern

// Queue function (works before script loads)
window.zenovay = window.zenovay || function() {
  (window.zenovay.q = window.zenovay.q || []).push(arguments);
};

// Now you can call zenovay() immediately, even before the script loads
window.zenovay('track', 'early_event', { source: 'inline' });
window.zenovay('page');

Svelte Integration

Setup in App.svelte

<script>
  import { onMount } from 'svelte';
  import { page } from '$app/stores';

  onMount(() => {
    // Track initial page view
    if (window.zenovay) {
      window.zenovay('page');
    }
  });

  // Track route changes
  $: if ($page && window.zenovay) {
    window.zenovay('page');
  }
</script>

Svelte Store

// stores/analytics.js
import { writable } from 'svelte/store';

function createAnalyticsStore() {
  const { subscribe } = writable(null);

  return {
    subscribe,
    track: (event, data) => {
      if (typeof window !== 'undefined' && window.zenovay) {
        window.zenovay('track', event, data);
      }
    },
    identify: (userId, properties) => {
      if (typeof window !== 'undefined' && window.zenovay) {
        window.zenovay('identify', userId, properties);
      }
    },
    trackGoal: (name, value) => {
      if (typeof window !== 'undefined' && window.zenovay) {
        window.zenovay('goal', name, { value });
      }
    }
  };
}

export const analytics = createAnalyticsStore();

Svelte Component Usage

<script>
  import { analytics } from '../stores/analytics';

  function handleClick() {
    analytics.track('button_click', { button: 'cta' });
  }
</script>

<button on:click={handleClick}>
  Click Me
</button>

SvelteKit Integration

// hooks.client.js
import { page } from '$app/stores';

page.subscribe(($page) => {
  if (typeof window !== 'undefined' && window.zenovay && $page) {
    window.zenovay('page');
  }
});

Angular Integration

Service Creation

// analytics.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';

declare global {
  interface Window {
    zenovay: {
      (command: 'track', event: string, data?: Record<string, any>): void;
      (command: 'page'): void;
      (command: 'goal', name: string, data?: Record<string, any>): void;
      (command: 'revenue', amount: number, currency: string, meta?: Record<string, any>): void;
      (command: 'identify', userId: string, properties?: Record<string, any>): void;
    };
  }
}

@Injectable({
  providedIn: 'root'
})
export class AnalyticsService {
  constructor(private router: Router) {
    this.setupRouteTracking();
  }

  private setupRouteTracking() {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd)
    ).subscribe((event: NavigationEnd) => {
      this.trackPageview(event.urlAfterRedirects);
    });
  }

  track(event: string, data?: Record<string, any>) {
    if (window.zenovay) {
      (window as any).zenovay('track', event, data);
    }
  }

  trackPageview() {
    if (window.zenovay) {
      (window as any).zenovay('page');
    }
  }

  trackGoal(name: string, value?: number) {
    if (window.zenovay) {
      (window as any).zenovay('goal', name, { value });
    }
  }

  identify(userId: string, properties?: Record<string, any>) {
    if (window.zenovay) {
      (window as any).zenovay('identify', userId, properties);
    }
  }
}

Component Usage

// signup.component.ts
import { Component } from '@angular/core';
import { AnalyticsService } from './analytics.service';

@Component({
  selector: 'app-signup',
  template: `<button (click)="handleSignup()">Sign Up</button>`
})
export class SignupComponent {
  constructor(private analytics: AnalyticsService) {}

  handleSignup() {
    this.analytics.track('signup_click', {
      source: 'header'
    });
  }
}

Module Setup

// app.module.ts
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { AnalyticsService } from './analytics.service';

export function initAnalytics(analytics: AnalyticsService) {
  return () => {
    // Initialize tracking
  };
}

@NgModule({
  providers: [
    AnalyticsService,
    {
      provide: APP_INITIALIZER,
      useFactory: initAnalytics,
      deps: [AnalyticsService],
      multi: true
    }
  ]
})
export class AppModule {}

Ember.js Integration

Service Creation

// app/services/analytics.js
import Service from '@ember/service';
import { inject as service } from '@ember/service';

export default class AnalyticsService extends Service {
  @service router;

  constructor() {
    super(...arguments);
    this.setupRouteTracking();
  }

  setupRouteTracking() {
    this.router.on('routeDidChange', () => {
      this.trackPageview(this.router.currentURL);
    });
  }

  track(event, data) {
    if (window.zenovay) {
      window.zenovay('track', event, data);
    }
  }

  trackPageview() {
    if (window.zenovay) {
      window.zenovay('page');
    }
  }

  identify(userId, properties) {
    if (window.zenovay) {
      window.zenovay('identify', userId, properties);
    }
  }
}

Alpine.js Integration

<div x-data="{ analytics: $store.analytics }">
  <button @click="analytics.track('click', { button: 'cta' })">
    Click Me
  </button>
</div>

<script>
document.addEventListener('alpine:init', () => {
  Alpine.store('analytics', {
    track(event, data) {
      if (window.zenovay) {
        window.zenovay('track', event, data);
      }
    },
    identify(userId, properties) {
      if (window.zenovay) {
        window.zenovay('identify', userId, properties);
      }
    }
  });
});
</script>

jQuery Integration

// Initialize tracking helper
$.zenovay = {
  track: function(event, data) {
    if (window.zenovay) {
      window.zenovay('track', event, data);
    }
  },
  identify: function(userId, properties) {
    if (window.zenovay) {
      window.zenovay('identify', userId, properties);
    }
  }
};

// Usage
$(document).ready(function() {
  $('#signup-btn').click(function() {
    $.zenovay.track('signup_click', {
      button: $(this).data('name')
    });
  });
});

Web Components

// analytics-tracker.js
class AnalyticsTracker extends HTMLElement {
  connectedCallback() {
    this.addEventListener('click', this.handleClick.bind(this));
  }

  handleClick() {
    const event = this.getAttribute('data-event');
    const dataAttrs = {};

    for (const attr of this.attributes) {
      if (attr.name.startsWith('data-prop-')) {
        const key = attr.name.replace('data-prop-', '');
        dataAttrs[key] = attr.value;
      }
    }

    if (window.zenovay && event) {
      window.zenovay('track', event, dataAttrs);
    }
  }
}

customElements.define('analytics-tracker', AnalyticsTracker);

Usage:

<analytics-tracker
  data-event="cta_click"
  data-prop-button="hero"
  data-prop-page="home"
>
  <button>Get Started</button>
</analytics-tracker>

Single Page Application (SPA) Patterns

History API Tracking

// Track history changes
(function() {
  const pushState = history.pushState;
  const replaceState = history.replaceState;

  history.pushState = function() {
    pushState.apply(history, arguments);
    trackPageChange();
  };

  history.replaceState = function() {
    replaceState.apply(history, arguments);
    trackPageChange();
  };

  window.addEventListener('popstate', trackPageChange);

  function trackPageChange() {
    if (window.zenovay) {
      // Small delay to ensure DOM updated
      setTimeout(() => {
        window.zenovay('page');
      }, 100);
    }
  }
})();

Hash-Based Routing

window.addEventListener('hashchange', function() {
  if (window.zenovay) {
    window.zenovay('page');
  }
});

TypeScript Definitions

// types/zenovay.d.ts
interface ZenovayEventData {
  [key: string]: string | number | boolean | undefined;
}

interface ZenovayRevenueData {
  order_id: string;
  value: number;
  currency?: string;
  items?: Array<{
    id: string;
    name: string;
    price: number;
    quantity: number;
  }>;
}

interface Zenovay {
  (command: 'track', event: string, data?: ZenovayEventData): void;
  (command: 'page'): void;
  (command: 'goal', name: string, data?: { value?: number }): void;
  (command: 'revenue', amount: number, currency: string, meta?: Record<string, unknown>): void;
  (command: 'identify', userId: string, properties?: ZenovayEventData): void;
}

declare global {
  interface Window {
    zenovay?: Zenovay;
  }
}

export {};

Wrapper Library Pattern

Create a reusable wrapper:

// lib/analytics.ts
class Analytics {
  private queue: Array<() => void> = [];
  private ready = false;

  constructor() {
    this.waitForScript();
  }

  private waitForScript() {
    const check = () => {
      if (window.zenovay) {
        this.ready = true;
        this.processQueue();
      } else {
        requestAnimationFrame(check);
      }
    };
    check();
  }

  private processQueue() {
    this.queue.forEach(fn => fn());
    this.queue = [];
  }

  private execute(fn: () => void) {
    if (this.ready) {
      fn();
    } else {
      this.queue.push(fn);
    }
  }

  track(event: string, data?: Record<string, any>) {
    this.execute(() => (window.zenovay as any)('track', event, data));
  }

  pageview() {
    this.execute(() => (window.zenovay as any)('page'));
  }

  identify(userId: string, properties?: Record<string, any>) {
    this.execute(() => (window.zenovay as any)('identify', userId, properties));
  }

  goal(name: string, value?: number) {
    this.execute(() => (window.zenovay as any)('goal', name, { value }));
  }

  revenue(amount: number, currency: string, meta?: Record<string, any>) {
    this.execute(() => (window.zenovay as any)('revenue', amount, currency, meta));
  }
}

export const analytics = new Analytics();

Testing Your Integration

Debug Mode

// Enable debug mode
localStorage.setItem('zenovay_debug', 'true');

// View events in console
// Reload page - events will be logged

Verification Steps

  1. Open browser DevTools
  2. Go to Network tab
  3. Filter by "zenovay" or "analytics"
  4. Perform actions
  5. Verify requests sent

Test Event

// Quick test
if (window.zenovay) {
  window.zenovay('track', 'test_event', {
    test: true,
    timestamp: Date.now()
  });
  console.log('Test event sent');
} else {
  console.error('Zenovay not loaded');
}

Troubleshooting

Script Not Loading

Check:

  • Script URL correct
  • No ad blocker interference
  • Website ID valid
  • Network requests visible

Events Not Tracking

Verify:

  • window.zenovay exists
  • Data format correct
  • No JavaScript errors
  • Domain allowed

SPA Navigation Issues

Ensure:

  • Route changes detected
  • trackPageview called on navigation
  • No duplicate tracking

Next Steps

Was this article helpful?