Skip to main content
A powerful JavaScript SDK for building custom applications within the Domo platform
npm version TypeScript domo.js (published as ryuu.js, developed as domo.js) is a comprehensive JavaScript library that enables developers to build interactive custom applications within the Domo platform. It provides seamless communication between your custom app and the Domo environment, supporting data fetching, real-time events, filters, variables, and cross-platform mobile integration.

Quick Start

Get started with ryuu.js in just a few lines:
import Domo from 'ryuu.js';

// Fetch data from a dataset
const data = await Domo.get('/data/v1/sales');
console.log(data); // Array of objects with your data

// Listen for dataset updates
Domo.onDataUpdated((alias) => {
  console.log(`Dataset ${alias} was updated!`);
  // Refresh your visualization
});

// Listen for filter changes
Domo.onFiltersUpdated((filters) => {
  console.log('Filters changed:', filters);
  // Apply filters to your data
});

Features

  • HTTP API Access - Authenticated requests to Domo datasets, datastores, and APIs
  • Real-time Events - Listen for dataset updates, filter changes, and variable updates
  • Filter Management - Get and set page-level filters programmatically
  • Variable Management - Access and update page variables
  • Custom App Data - Send custom data between apps on the same page
  • Navigation - Programmatically navigate within Domo
  • Mobile Support - Full iOS and Android compatibility
  • TypeScript Ready - Complete type definitions included
  • Zero Dependencies - Minimal bundle size with no runtime dependencies
  • Extensible - Override or extend functionality via extend() method

Installation

NPM

npm install ryuu.js
Then import in your application:
// ES modules
import Domo from 'ryuu.js';

// CommonJS
const Domo = require('ryuu.js').default;

CDN / Script Tag

<script src="https://unpkg.com/ryuu.js"></script>
<script>
  // Domo is available globally
  Domo.get('/data/v1/sales').then(data => &#123;
    console.log(data);
  &#125;);
</script>

TypeScript

TypeScript definitions are included automatically:
import Domo, { Filter, Variable, RequestOptions } from 'ryuu.js';

Core Concepts

Communication Architecture

Your custom app runs in an iframe within the Domo platform. ryuu.js establishes a bidirectional communication channel using:
  • MessageChannel API - Primary communication mechanism for desktop/web
  • webkit.messageHandlers - iOS native integration
  • Global objects - Android/Flutter integration

Request-Reply Pattern

Async operations (like updating filters) use an ASK-ACK-REPLY pattern:
  1. ASK - Your app sends a request with a unique ID
  2. ACK - Parent acknowledges receipt (optional callback)
  3. REPLY - Parent sends response when operation completes (optional callback)
Domo.requestFiltersUpdate(
  filters,
  true,
  () => console.log('Request acknowledged'),
  (response) => console.log('Request completed:', response),
);

Environment Context

Access information about the current user and environment via Domo.env:
console.log(Domo.env.userId); // Current user ID
console.log(Domo.env.customer); // Customer name
console.log(Domo.env.pageId); // Current page ID
console.log(Domo.env.locale); // Locale (e.g., 'en-US')
console.log(Domo.env.platform); // Platform (e.g., 'desktop', 'mobile')
Security Note: Environment properties come from URL parameters and can be spoofed. Always verify with the API for secure operations:
const authenticatedUser = await Domo.get('/domo/environment/v1/');

API Reference

HTTP Methods

All HTTP methods return Promises and support multiple data formats.

Domo.get(url, options?)

Fetch data from a Domo dataset or API endpoint. Parameters:
  • url (string) - API endpoint URL
  • options (object, optional) - Request options
Returns: Promise<ResponseBody> Basic Usage:
// Returns array of objects by default
const data = await Domo.get('/data/v1/sales');
console.log(data); // [{ id: 1, amount: 100, ... }, ...]
Format Options:
// CSV format
const csv = await Domo.get('/data/v1/sales', { format: 'csv' });

// Array of arrays with metadata
const arrayData = await Domo.get('/data/v1/sales', {
  format: 'array-of-arrays',
});
console.log(arrayData.columns); // ['id', 'amount', 'date']
console.log(arrayData.rows); // [[1, 100, '2024-01-01'], ...]

// Excel format (returns Blob)
const excel = await Domo.get('/data/v1/sales', { format: 'excel' });
Supported Formats:
  • 'array-of-objects' (default) - Returns ObjectResponseBody[]
  • 'array-of-arrays' - Returns ArrayResponseBody with metadata
  • 'csv' - Returns CSV string
  • 'excel' - Returns Excel Blob
  • 'plain' - Returns plain text string
Query Parameters:
const data = await Domo.get('/data/v1/sales', {
  query: {
    limit: 100,
    offset: 0,
    fields: 'id,amount,date',
  },
});
Best Practice: Always filter and paginate large datasets to avoid slow responses:
const data = await Domo.get('/data/v1/sales', {
  query: {
    limit: 1000,
    offset: 0,
    filter: 'date > "2024-01-01"',
  },
});

Domo.getAll(urls, options?)

Fetch multiple datasets or endpoints in parallel. Parameters:
  • urls (string[]) - Array of API endpoint URLs
  • options (object, optional) - Request options applied to all requests
Returns: Promise<ResponseBody[]> Example:
const [sales, inventory, customers] = await Domo.getAll([
  '/data/v1/sales',
  '/data/v1/inventory',
  '/data/v1/customers',
]);

console.log(sales); // First dataset
console.log(inventory); // Second dataset
console.log(customers); // Third dataset
With Options:
const results = await Domo.getAll(['/data/v1/sales', '/data/v1/inventory'], {
  format: 'csv',
});
// All results will be CSV strings

Domo.post(url, body?, options?)

Send a POST request to create data. Parameters:
  • url (string) - API endpoint URL
  • body (object | string, optional) - Request body
  • options (object, optional) - Request options
Returns: Promise<ResponseBody> Example:
// Create a document in Domo DataStore
const result = await Domo.post(
  '/domo/datastores/v1/collections/users/documents/',
  {
    name: 'John Doe',
    email: 'john@example.com',
    role: 'Admin',
  },
);

console.log(result); // Created document with ID

Domo.put(url, body?, options?)

Send a PUT request to update data. Parameters:
  • url (string) - API endpoint URL
  • body (object | string, optional) - Request body
  • options (object, optional) - Request options
Returns: Promise<ResponseBody> Example:
// Update an existing document
const result = await Domo.put(
  '/domo/datastores/v1/collections/users/documents/abc123',
  {
    name: 'Jane Doe',
    role: 'Manager',
  },
);

Domo.delete(url, options?)

Send a DELETE request to remove data. Parameters:
  • url (string) - API endpoint URL
  • options (object, optional) - Request options
Returns: Promise<ResponseBody> Example:
// Delete a document
await Domo.delete('/domo/datastores/v1/collections/users/documents/abc123');

Domo.domoHttp(method, url, options?, body?)

Low-level HTTP method for full control over requests. All other HTTP methods use this internally. Parameters:
  • method (RequestMethods) - HTTP method
  • url (string) - API endpoint URL
  • options (object, optional) - Request options
  • body (object | string, optional) - Request body
Returns: Promise<ResponseBody> Example:
import { RequestMethods } from 'ryuu.js';

const result = await Domo.domoHttp(
  RequestMethods.PATCH,
  '/custom/endpoint',
  { format: 'array-of-objects' },
  { data: 'custom body' },
);

Event Listeners

Event listeners enable real-time reactivity to changes in the Domo platform. All listener methods return an unsubscribe function.

Domo.onDataUpdated(callback)

Listen for dataset update events. Parameters:
  • callback (function) - Called when a dataset is updated
    • Receives: datasetAlias (string) - Alias of the updated dataset
Returns: function - Unsubscribe function Example:
const unsubscribe = Domo.onDataUpdated((datasetAlias) => {
  console.log(`Dataset ${datasetAlias} was updated`);
  // Reload data for this dataset
  loadData();
});

// Later, stop listening
unsubscribe();
Use Case: Refresh your app’s visualizations when underlying data changes without requiring a page reload.

Domo.onFiltersUpdated(callback)

Listen for page filter changes. Parameters:
  • callback (function) - Called when filters change
    • Receives: filters (Filter[]) - Array of filter objects
Returns: function - Unsubscribe function Example:
Domo.onFiltersUpdated((filters) => {
  console.log('Filters updated:', filters);

  // Find specific filter
  const categoryFilter = filters.find((f) => f.column === 'category');

  if (categoryFilter) {
    console.log('Category filter:', categoryFilter.values);
    // Apply filter to your visualization
    applyFilters(categoryFilter.values);
  }
});
Filter Object Structure:
{
  column: "category",              // Column name being filtered
  operator: "IN",                  // Filter operator
  values: ["ALERT", "WARNING"],    // Array of filter values
  dataType: "STRING",              // Data type: STRING, NUMERIC, DATE, DATETIME
  dataSourceId: "46d91556-...",    // Source dataset ID (optional)
  label: "category"                // Display label (optional)
}
Supported Operators: String Operators:
  • "IN" - Value is in list
  • "NOT_IN" - Value is not in list
  • "CONTAINS" - Value contains string
  • "NOT_CONTAINS" - Value doesn’t contain string
  • "STARTS_WITH" - Value starts with string
  • "NOT_STARTS_WITH" - Value doesn’t start with string
  • "ENDS_WITH" - Value ends with string
  • "NOT_ENDS_WITH" - Value doesn’t end with string
Numeric/Date Operators:
  • "EQUALS" - Equals value
  • "NOT_EQUALS" - Not equals value
  • "GREATER_THAN" - Greater than value
  • "GREAT_THAN_EQUALS_TO" - Greater than or equals value
  • "LESS_THAN" - Less than value
  • "LESS_THAN_EQUALS_TO" - Less than or equals value
  • "BETWEEN" - Between two values

Domo.onVariablesUpdated(callback)

Listen for page variable changes. Parameters:
  • callback (function) - Called when variables change
    • Receives: variables (object) - Variables object with IDs as keys
Returns: function - Unsubscribe function Example:
Domo.onVariablesUpdated((variables) => {
  console.log('Variables updated:', variables);

  // Access specific variable by ID
  const themeVariable = variables['391'];
  if (themeVariable) {
    const theme = themeVariable.parsedExpression.value;
    setTheme(theme);
  }
});
Variables Object Structure:
{
  "391": {
    "parsedExpression": {
      "exprType": "NUMERIC_VALUE",
      "value": "9"
    }
  },
  "392": {
    "parsedExpression": {
      "exprType": "STRING_VALUE",
      "value": "dark"
    }
  }
}
Note: Variable IDs (like “391”) are defined by Domo. Inspect the variables object in your app to find the correct IDs.

Domo.onAppDataUpdated(callback)

Listen for custom app data updates. Parameters:
  • callback (function) - Called when app data is received
    • Receives: data (any) - Custom data object
Returns: function - Unsubscribe function Example:
Domo.onAppDataUpdated((data) => {
  console.log('Received app data:', data);

  // Handle custom data from other apps
  if (data.action === 'highlight') {
    highlightRow(data.rowId);
  }
});
Use Case: Enable communication between multiple custom apps on the same Domo page.

Emitters

Emitters send messages to the parent Domo platform to trigger actions or update state.

Domo.requestFiltersUpdate(filters, pageStateUpdate?, onAck?, onReply?)

Update page-level filters programmatically. Parameters:
  • filters (Filter[]) - Array of filter objects
  • pageStateUpdate (boolean, optional) - Optional boolean indicating if the page state should be updated by the filter. When false, on the card level filter state will be updated. (default: true)
  • onAck (function, optional) - Called when request is acknowledged
  • onReply (function, optional) - Called when request completes
    • Receives: response (any) - Response data
Returns: string - Request ID for tracking Example:
// Basic usage
Domo.requestFiltersUpdate([
  {
    column: 'category',
    operator: 'IN',
    values: ['ALERT', 'WARNING'],
    dataType: 'STRING',
  },
  {
    column: 'amount',
    operator: 'GREATER_THAN',
    values: [1000],
    dataType: 'NUMERIC',
  },
]);
With Callbacks:
const requestId = Domo.requestFiltersUpdate(
  filters,
  true,
  () => console.log('Filter update acknowledged'),
  (response) => console.log('Filter update completed:', response),
);

console.log('Request ID:', requestId);
Filter Requirements: All filter objects must include:
  • column (string, required) - Column name to filter on
  • operator (string, required) - Filter operator (see supported operators above)
  • values (array, required) - Values to filter by
  • dataType (string, required) - Data type: "STRING", "NUMERIC", "DATE", or "DATETIME"

Domo.requestVariablesUpdate(variables, onAck?, onReply?)

Update page variables programmatically. Parameters:
  • variables (Variable[]) - Array of variable objects
  • onAck (function, optional) - Called when request is acknowledged
  • onReply (function, optional) - Called when request completes
Returns: string - Request ID for tracking Example:
Domo.requestVariablesUpdate([
  {
    functionId: 123,
    value: 100,
  },
  {
    functionId: 124,
    value: 'dark',
  },
]);
Variable Object Structure:
{
  functionId: number,  // Variable function ID from Domo
  value: any          // New value for the variable
}

Domo.requestAppDataUpdate(data, onAck?, onReply?)

Send custom app data to other apps on the same page. Parameters:
  • data (any) - Custom data object
  • onAck (function, optional) - Called when request is acknowledged
  • onReply (function, optional) - Called when request completes
Returns: void Example:
// Send custom data
Domo.requestAppDataUpdate({
  action: 'highlight',
  rowId: 123,
  timestamp: Date.now(),
});
Use Case: Coordinate interactions between multiple custom apps on the same Domo page.

Domo.navigate(url, isNewWindow?)

Navigate to a different page within Domo. Parameters:
  • url (string) - Domo page URL or route
  • isNewWindow (boolean, optional) - Open in new tab/window (default: false)
Example:
// Navigate to a profile page
Domo.navigate('/profile/3234');

// Open in new tab
Domo.navigate('/page/123456789', true);
Important Notes:
  • Use Domo.navigate() instead of HTML links to change the page hosting the custom app
  • For mobile web platforms, routes are automatically prefixed with /m# (e.g., /m#/profile/3234)
  • External links are restricted to whitelisted domains (configure in Admin > Network Security > Custom Apps authorized domains)

Environment

Domo.env

Access environment information about the current context. Type: QueryParams (object) Available Properties:
Domo.env.pageId; // Current page ID
Domo.env.userId; // Current user ID
Domo.env.customer; // Customer name
Domo.env.locale; // Locale (e.g., 'en-US')
Domo.env.environment; // Environment (e.g., 'dev3', 'prod')
Domo.env.platform; // Platform (e.g., 'desktop', 'mobile')
Example:
console.log(`User ${Domo.env.userId} on ${Domo.env.platform}`);

// Conditional logic based on platform
if (Domo.env.platform === 'mobile') {
  renderMobileLayout();
} else {
  renderDesktopLayout();
}
Security Warning: These properties come from URL query parameters and can be spoofed. For secure user identification, always verify with the API:
const authenticatedUser = await Domo.get('/domo/environment/v1/');
console.log('Verified user:', authenticatedUser);

Utilities

Domo.extend(overrides)

Extend or override static methods and properties of the Domo class. Parameters:
  • overrides (object) - Object with methods/properties to override
Returns: void Example:
import Domo, { get as originalGet } from 'ryuu.js';

// Add logging to all GET requests
Domo.extend({
  get: async function (url, options) {
    console.log(`[API] Fetching: ${url}`);
    const startTime = Date.now();

    try {
      const result = await originalGet.call(this, url, options);
      console.log(`[API] Success: ${url} (${Date.now() - startTime}ms)`);
      return result;
    } catch (error) {
      console.error(`[API] Error: ${url} (${Date.now() - startTime}ms)`, error);
      throw error;
    }
  },
});

// Now all Domo.get() calls include logging
const data = await Domo.get('/data/v1/sales');
Use Cases:
  • Add logging to all requests
  • Implement retry logic
  • Add caching layer
  • Mock responses for testing
  • Add custom error handling

Domo.getRequests()

Get all tracked requests (ASK-ACK-REPLY pattern). Returns: AskReplyMap - Object with request IDs as keys Example:
const requestId = Domo.requestFiltersUpdate(filters);

// Check request status
const requests = Domo.getRequests();
console.log(requests[requestId]);
// {
//   request: { status: 'SENT', timestamp: 1234567890 },
//   response: { status: 'SUCCESS', timestamp: 1234567900, data: {...} }
// }

Domo.getRequest(id)

Get a specific tracked request by ID. Parameters:
  • id (string) - Request ID
Returns: Request object or undefined Example:
const requestId = Domo.requestFiltersUpdate(filters);
const request = Domo.getRequest(requestId);
console.log(request.request.status); // 'PENDING', 'SENT'

Domo.__util

Internal utilities exposed for advanced use cases. Available Utilities:
Domo.__util.isSuccess(statusCode); // Check if HTTP status is success
Domo.__util.isVerifiedOrigin(origin); // Verify origin is trusted Domo domain
Domo.__util.getQueryParams(); // Get current query parameters
Domo.__util.setFormatHeaders(format, headers); // Set Accept headers
Domo.__util.generateUniqueId(); // Generate unique request ID
Domo.__util.isIOS(); // Check if running on iOS
Note: These are internal utilities and may change between versions. Use at your own risk.

TypeScript Support

ryuu.js includes comprehensive TypeScript definitions for a better development experience.

Importing Types

import Domo, {
  // Interfaces
  Filter,
  Variable,
  RequestOptions,
  ObjectResponseBody,
  ArrayResponseBody,
  QueryParams,

  // Enums
  DataFormats,
  DomoDataTypes,
  RequestMethods,

  // Type Guards
  isFilter,
  isFilterArray,
  isVariable,
  isVariableArray,
} from 'ryuu.js';

Typed Requests

// Type-safe request options
const options: RequestOptions<'array-of-objects'> = {
  format: 'array-of-objects',
  query: { limit: 100 },
};

// Return type is automatically inferred
const data: ObjectResponseBody[] = await Domo.get('/data/v1/sales', options);

// Format-specific types
const csv: string = await Domo.get('/data/v1/sales', { format: 'csv' });
const arrays: ArrayResponseBody = await Domo.get('/data/v1/sales', {
  format: 'array-of-arrays',
});
const excel: Blob = await Domo.get('/data/v1/sales', { format: 'excel' });

Typed Filters

const filters: Filter[] = [
  {
    column: 'category',
    operator: 'IN',
    values: ['ALERT', 'WARNING'],
    dataType: 'STRING',
  },
  {
    column: 'amount',
    operator: 'GREATER_THAN',
    values: [1000],
    dataType: 'NUMERIC',
  },
];

Domo.requestFiltersUpdate(filters);

Typed Variables

const variables: Variable[] = [
  { functionId: 123, value: 100 },
  { functionId: 124, value: 'dark' },
];

Domo.requestVariablesUpdate(variables);

Custom Types

// Define your data shape
interface SalesRecord {
  id: number;
  amount: number;
  date: string;
  category: string;
}

// Type-safe data access
const sales = (await Domo.get('/data/v1/sales')) as SalesRecord[];

sales.forEach((record) => {
  console.log(`Sale ${record.id}: $${record.amount}`);
});

Mobile Platform Support

ryuu.js provides full support for iOS and Android mobile platforms through platform-specific APIs.

iOS Integration

On iOS, ryuu.js uses webkit.messageHandlers for native communication:
// Automatically handled by ryuu.js
Domo.requestFiltersUpdate(filters);
// Internally uses: webkit.messageHandlers.domofilter.postMessage()

Domo.requestVariablesUpdate(variables);
// Internally uses: webkit.messageHandlers.domovariable.postMessage()

Android/Flutter Integration

On Android, ryuu.js uses global objects injected by the native app:
// Automatically handled by ryuu.js
Domo.requestVariablesUpdate(variables);
// Internally uses: window.domovariable.postMessage()

Domo.requestFiltersUpdate(filters);
// Internally uses: window.domofilter.postMessage()

Platform Detection

ryuu.js automatically detects the platform and uses the appropriate communication method:
// Check if running on mobile
if (Domo.env.platform === 'mobile') {
  // Mobile-specific logic
  renderMobileView();
} else {
  // Desktop-specific logic
  renderDesktopView();
}

// Internal iOS detection (exposed via __util)
if (Domo.__util.isIOS()) {
  // iOS-specific logic
}

Mobile Considerations

  1. Navigation: Routes are automatically prefixed with /m# on mobile web
  2. Touch Events: Consider touch-friendly UI for mobile apps
  3. Performance: Mobile devices may have limited resources - optimize data fetching
  4. Testing: Test on actual devices or simulators for best results

Error Handling

All HTTP methods return Promises. Use try/catch with async/await for error handling.

Basic Error Handling

try {
  const data = await Domo.get('/data/v1/sales');
  console.log('Data loaded:', data);
} catch (error) {
  console.error('Failed to load data:', error);
}

Error Object Structure

Error objects include comprehensive information:
try {
  const data = await Domo.get('/data/v1/nonexistent');
} catch (error) {
  console.error('Status:', error.status); // 404
  console.error('Status Text:', error.statusText); // 'Not Found'
  console.error('Message:', error.message); // Error description
  console.error('Body:', error.body); // Response body
  console.error('Headers:', error.headers); // Response headers
}

Handling Specific Error Types

try {
  const data = await Domo.get('/data/v1/sales');
} catch (error) {
  if (error.status === 404) {
    console.error('Dataset not found');
    showError('The requested dataset does not exist');
  } else if (error.status === 403) {
    console.error('Access denied');
    showError('You do not have permission to access this dataset');
  } else if (error.status === 401) {
    console.error('Authentication failed');
    showError('Your session has expired. Please refresh the page.');
  } else if (error.status >= 500) {
    console.error('Server error');
    showError('A server error occurred. Please try again later.');
  } else {
    console.error('Unexpected error:', error);
    showError('An unexpected error occurred');
  }
}

Retry Logic

Implement retry logic for transient errors:
async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await Domo.get(url, options);
    } catch (error) {
      if (i === maxRetries - 1) throw error;

      // Only retry on server errors
      if (error.status >= 500) {
        console.log(`Retry ${i + 1}/${maxRetries}...`);
        await new Promise((resolve) => setTimeout(resolve, 1000 * (i + 1)));
      } else {
        throw error;
      }
    }
  }
}

// Usage
try {
  const data = await fetchWithRetry('/data/v1/sales');
} catch (error) {
  console.error('Failed after retries:', error);
}

Advanced Usage

Custom Fetch Implementation

Provide your own fetch implementation for testing or custom behavior:
// Mock fetch for testing
const mockFetch = async (url, options) => {
  return {
    ok: true,
    status: 200,
    headers: new Headers(),
    json: async () => [{ id: 1, name: 'Test' }],
  };
};

const data = await Domo.get('/data/v1/sales', {
  fetchImpl: mockFetch,
});

Caching Layer

Add a caching layer using extend():
const cache = new Map();

Domo.extend({
  get: async function (url, options) {
    // Check cache
    const cacheKey = `${url}:${JSON.stringify(options)}`;
    if (cache.has(cacheKey)) {
      console.log('Cache hit:', url);
      return cache.get(cacheKey);
    }

    // Fetch and cache
    const result = await originalGet.call(this, url, options);
    cache.set(cacheKey, result);

    return result;
  },
});

Request Interceptors

Add interceptors for all requests:
import Domo, { domoHttp as originalDomoHttp } from 'ryuu.js';

Domo.extend({
  domoHttp: async function (method, url, options, body) {
    // Before request
    console.log(`[${method}] ${url}`);
    const startTime = Date.now();

    // Add custom headers
    options = {
      ...options,
      headers: {
        ...options?.headers,
        'X-Custom-Header': 'value',
      },
    };

    try {
      // Make request
      const result = await originalDomoHttp.call(
        this,
        method,
        url,
        options,
        body,
      );

      // After successful request
      console.log(`[${method}] ${url} - Success (${Date.now() - startTime}ms)`);
      return result;
    } catch (error) {
      // After failed request
      console.error(
        `[${method}] ${url} - Error (${Date.now() - startTime}ms)`,
        error,
      );
      throw error;
    }
  },
});

Type Guards

Use type guards to validate runtime data:
import { isFilter, isFilterArray, guardAgainstInvalidFilters } from 'ryuu.js';

// Validate individual filter
if (isFilter(data)) {
  // TypeScript knows data is a Filter
  console.log(data.column);
}

// Validate array of filters
if (isFilterArray(data)) {
  // TypeScript knows data is Filter[]
  data.forEach((filter) => console.log(filter.column));
}

// Throw error if invalid
try {
  guardAgainstInvalidFilters(data);
  // Data is valid, proceed
} catch (error) {
  console.error('Invalid filters:', error.message);
}

Complete Example

Here’s a comprehensive example showing a real-world custom app:
import Domo from 'ryuu.js';

class SalesDashboard {
  constructor() {
    this.data = [];
    this.filters = [];
    this.initialize();
  }

  async initialize() {
    // Set up event listeners
    this.setupEventListeners();

    // Load initial data
    await this.loadData();

    // Render dashboard
    this.render();
  }

  setupEventListeners() {
    // Listen for dataset updates
    Domo.onDataUpdated((datasetAlias) => {
      console.log(`Dataset ${datasetAlias} updated`);
      this.loadData();
    });

    // Listen for filter changes
    Domo.onFiltersUpdated((filters) => {
      console.log('Filters updated:', filters);
      this.filters = filters;
      this.applyFilters();
    });

    // Listen for variable changes
    Domo.onVariablesUpdated((variables) => {
      console.log('Variables updated:', variables);
      this.updateTheme(variables);
    });

    // Set up UI event handlers
    document.getElementById('refreshBtn').addEventListener('click', () => {
      this.loadData();
    });

    document.getElementById('exportBtn').addEventListener('click', () => {
      this.exportData();
    });

    document.getElementById('filterBtn').addEventListener('click', () => {
      this.updateFilters();
    });
  }

  async loadData() {
    try {
      // Load multiple datasets in parallel
      const [sales, customers, products] = await Domo.getAll([
        '/data/v1/sales',
        '/data/v1/customers',
        '/data/v1/products',
      ]);

      this.data = {
        sales,
        customers,
        products,
      };

      this.render();
    } catch (error) {
      console.error('Failed to load data:', error);
      this.showError('Unable to load dashboard data');
    }
  }

  applyFilters() {
    // Find category filter
    const categoryFilter = this.filters.find((f) => f.column === 'category');

    if (categoryFilter && categoryFilter.values.length > 0) {
      // Filter local data
      const filteredSales = this.data.sales.filter((sale) =>
        categoryFilter.values.includes(sale.category),
      );

      this.renderSales(filteredSales);
    } else {
      this.renderSales(this.data.sales);
    }
  }

  updateFilters() {
    // Get selected categories from UI
    const selectedCategories = Array.from(
      document.querySelectorAll('.category-checkbox:checked'),
    ).map((cb) => cb.value);

    // Update page filters
    Domo.requestFiltersUpdate(
      [
        {
          column: 'category',
          operator: 'IN',
          values: selectedCategories,
          dataType: 'STRING',
        },
      ],
      true,
      () => console.log('Filter update acknowledged'),
      (response) => console.log('Filter update completed:', response),
    );
  }

  async exportData() {
    try {
      // Export as CSV
      const csv = await Domo.get('/data/v1/sales', { format: 'csv' });

      // Download file
      const blob = new Blob([csv], { type: 'text/csv' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `sales-export-${Date.now()}.csv`;
      a.click();
      URL.revokeObjectURL(url);

      console.log('Export successful');
    } catch (error) {
      console.error('Export failed:', error);
      this.showError('Failed to export data');
    }
  }

  updateTheme(variables) {
    // Check for theme variable (example ID: 391)
    const themeVar = variables['391'];

    if (themeVar) {
      const theme = themeVar.parsedExpression.value;
      document.body.className = `theme-${theme}`;
    }
  }

  render() {
    if (!this.data.sales) return;

    // Render sales chart
    this.renderSales(this.data.sales);

    // Render customer stats
    this.renderCustomerStats(this.data.customers);

    // Render product list
    this.renderProducts(this.data.products);
  }

  renderSales(sales) {
    const salesContainer = document.getElementById('salesChart');

    // Calculate totals
    const total = sales.reduce((sum, sale) => sum + sale.amount, 0);
    const count = sales.length;
    const average = total / count;

    salesContainer.innerHTML = `
      <div class="stats">
        <div class="stat">
          <h3>Total Sales</h3>
          <p>$${total.toLocaleString()}</p>
        </div>
        <div class="stat">
          <h3>Number of Sales</h3>
          <p>${count.toLocaleString()}</p>
        </div>
        <div class="stat">
          <h3>Average Sale</h3>
          <p>$${average.toFixed(2)}</p>
        </div>
      </div>
    `;

    // Render chart (using your charting library)
    // this.renderChart(sales);
  }

  renderCustomerStats(customers) {
    const customerContainer = document.getElementById('customerStats');

    customerContainer.innerHTML = `
      <h3>Total Customers: ${customers.length}</h3>
      <ul>
        ${customers
          .slice(0, 10)
          .map(
            (c) => `
          <li>${c.name} - ${c.email}</li>
        `,
          )
          .join('')}
      </ul>
    `;
  }

  renderProducts(products) {
    const productContainer = document.getElementById('productList');

    productContainer.innerHTML = `
      <table>
        <thead>
          <tr>
            <th>Product</th>
            <th>Price</th>
            <th>Stock</th>
          </tr>
        </thead>
        <tbody>
          ${products
            .map(
              (p) => `
            <tr>
              <td>${p.name}</td>
              <td>$${p.price}</td>
              <td>${p.stock}</td>
            </tr>
          `,
            )
            .join('')}
        </tbody>
      </table>
    `;
  }

  showError(message) {
    const errorContainer = document.getElementById('error');
    errorContainer.textContent = message;
    errorContainer.style.display = 'block';

    setTimeout(() => {
      errorContainer.style.display = 'none';
    }, 5000);
  }
}

// Initialize app
new SalesDashboard();

Migration Guide

Deprecated Methods

The following methods have been renamed for consistency. Old methods still work but are deprecated and will be removed in a future version.
Deprecated MethodNew MethodDescription
Domo.onDataUpdateDomo.onDataUpdatedListen for dataset changes
Domo.onFiltersUpdateDomo.onFiltersUpdatedListen for filter changes
Domo.onAppDataDomo.onAppDataUpdatedListen for app data changes
Domo.filterContainerDomo.requestFiltersUpdateSet page filters
Domo.sendVariablesDomo.requestVariablesUpdateUpdate page variables
Domo.sendAppDataDomo.requestAppDataUpdateSend custom app data

Migration Steps

  1. Find and Replace:
// Old
Domo.onDataUpdate((alias) => { ... });
Domo.onFiltersUpdate((filters) => { ... });
Domo.onAppData((data) => { ... });
Domo.filterContainer(filters);
Domo.sendVariables(variables);
Domo.sendAppData(data);

// New
Domo.onDataUpdated((alias) => { ... });
Domo.onFiltersUpdated((filters) => { ... });
Domo.onAppDataUpdated((data) => { ... });
Domo.requestFiltersUpdate(filters);
Domo.requestVariablesUpdate(variables);
Domo.requestAppDataUpdate(data);
  1. Update Dependencies:
npm update ryuu.js
  1. Test Thoroughly:
After migration, test all functionality to ensure everything works correctly.

Contributing

Contributions are welcome! Please follow these guidelines:
  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Make your changes
  4. Add tests for new functionality
  5. Run tests: npm test
  6. Build: npm run build
  7. Commit: git commit -m "Add my feature"
  8. Push: git push origin feature/my-feature
  9. Open a Pull Request

Development Setup

# Clone repository
git clone https://github.com/your-org/domo.js.git
cd domo.js

# Install dependencies
npm install

# Run tests
npm test

# Run tests with coverage
npm run coverage

# Build
npm run build

# Start demo server
npm run demo

Made with ❤️ by the Domo team