Integration Examples

Real-world code examples for integrating Plaros embeds into popular frameworks and platforms.

React Integration

Basic React Component

import React, { useMemo } from 'react';

const PlarosEmbed = ({ 
  tenantSlug, 
  zonePath, 
  playbookId, 
  userId, 
  customColors = {},
  themeMode,
  size = 'medium',
  className,
  onLoad 
}) => {
  const embedUrl = useMemo(() => {
    const url = new URL(`https://play.plaros.com/v1/embed/${tenantSlug}/playbooks`);
    
    // Required parameter
    url.searchParams.set('zonePath', zonePath);
    
    // Optional parameters
    if (playbookId) {
      url.searchParams.set('playbookId', playbookId);
    }
    
    if (userId) {
      url.searchParams.set('userId', userId);
    }
    
    if (customColors.primary) {
      url.searchParams.set('primaryColor', customColors.primary.replace('#', ''));
    }
    
    if (customColors.secondary) {
      url.searchParams.set('secondaryColor', customColors.secondary.replace('#', ''));
    }
    
    if (themeMode) {
      url.searchParams.set('themeMode', themeMode);
    }
    
    return url.toString();
  }, [tenantSlug, zonePath, playbookId, userId, customColors, themeMode]);

  const sizeConfig = {
    small: { maxWidth: '400px', aspectRatio: '177.78%' },
    medium: { maxWidth: '600px', aspectRatio: '133.33%' },
    large: { maxWidth: '800px', aspectRatio: '75%' }
  };

  const config = sizeConfig[size] || sizeConfig.medium;

  return (
    <div 
      className={`playbook-embed-container ${className || ''}`} 
      style={{
        position: 'relative',
        width: '100%',
        maxWidth: config.maxWidth,
        margin: '0 auto'
      }}
    >
      <div style={{
        position: 'relative',
        paddingBottom: config.aspectRatio,
        height: 0,
        overflow: 'hidden'
      }}>
        <iframe
          src={embedUrl}
          style={{
            position: 'absolute',
            top: 0,
            left: 0,
            width: '100%',
            height: '100%',
            border: 0,
            borderRadius: '8px'
          }}
          allow="fullscreen"
          title="Interactive Playbook"
          onLoad={onLoad}
        />
      </div>
    </div>
  );
};

export default PlarosEmbed;

React with Hooks and Context

import React, { createContext, useContext, useState } from 'react';

// Theme context for app-wide embed theming
const EmbedThemeContext = createContext();

export const EmbedThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState({
    primaryColor: '#2196F3',
    secondaryColor: '#9C27B0',
    mode: 'light'
  });

  return (
    <EmbedThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </EmbedThemeContext.Provider>
  );
};

// Custom hook for embed configuration
const useEmbedConfig = (baseConfig) => {
  const { theme } = useContext(EmbedThemeContext);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  const embedUrl = useMemo(() => {
    const url = new URL(`https://play.plaros.com/v1/embed/${baseConfig.tenantSlug}/playbooks`);
    
    url.searchParams.set('zonePath', baseConfig.zonePath);
    
    if (baseConfig.playbookId) {
      url.searchParams.set('playbookId', baseConfig.playbookId);
    }
    
    if (baseConfig.userId) {
      url.searchParams.set('userId', baseConfig.userId);
    }
    
    // Apply theme from context
    url.searchParams.set('primaryColor', theme.primaryColor.replace('#', ''));
    url.searchParams.set('secondaryColor', theme.secondaryColor.replace('#', ''));
    url.searchParams.set('themeMode', theme.mode);
    
    return url.toString();
  }, [baseConfig, theme]);

  const handleLoad = () => {
    setLoading(false);
    setError(null);
  };

  const handleError = (err) => {
    setLoading(false);
    setError(err.message || 'Failed to load embed');
  };

  return { embedUrl, loading, error, handleLoad, handleError };
};

// Enhanced component using the hook
const ThemedPlarosEmbed = (props) => {
  const { embedUrl, loading, error, handleLoad, handleError } = useEmbedConfig(props);

  if (error) {
    return (
      <div className="embed-error" style={{ 
        padding: '20px', 
        background: '#fee', 
        border: '1px solid #fcc',
        borderRadius: '8px',
        textAlign: 'center'
      }}>
        <p>Failed to load playbook: {error}</p>
        <button onClick={() => window.location.reload()}>
          Retry
        </button>
      </div>
    );
  }

  return (
    <div className="themed-embed-container">
      {loading && (
        <div className="embed-loading" style={{
          position: 'absolute',
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%)',
          zIndex: 1
        }}>
          Loading interactive content...
        </div>
      )}
      
      <PlarosEmbed 
        {...props}
        onLoad={handleLoad}
        onError={handleError}
      />
    </div>
  );
};

Vue.js Integration

Vue 3 Composition API

<template>
  <div class="playbook-embed-container" :class="containerClass">
    <div class="playbook-embed-wrapper" :style="wrapperStyle">
      <iframe
        :src="embedUrl"
        :style="iframeStyle"
        :title="embedTitle"
        allow="fullscreen"
        @load="handleLoad"
        @error="handleError"
      />
    </div>
    
    <div v-if="loading" class="embed-loading">
      <slot name="loading">
        <div class="loading-spinner">Loading...</div>
      </slot>
    </div>
  </div>
</template>

<script>
import { computed, ref, watch } from 'vue';

export default {
  name: 'PlarosEmbed',
  props: {
    tenantSlug: { type: String, required: true },
    zonePath: { type: String, required: true },
    playbookId: { type: String, default: null },
    userId: { type: String, default: null },
    primaryColor: { type: String, default: null },
    secondaryColor: { type: String, default: null },
    themeMode: { type: String, default: null },
    size: { type: String, default: 'medium' },
    analytics: { type: Boolean, default: true },
    debug: { type: Boolean, default: false }
  },
  emits: ['load', 'error'],
  setup(props, { emit }) {
    const loading = ref(true);
    
    const embedUrl = computed(() => {
      const url = new URL(`https://play.plaros.com/v1/embed/${props.tenantSlug}/playbooks`);
      
      url.searchParams.set('zonePath', props.zonePath);
      
      if (props.playbookId) {
        url.searchParams.set('playbookId', props.playbookId);
      }
      
      if (props.userId) {
        url.searchParams.set('userId', props.userId);
      }
      
      if (props.primaryColor) {
        url.searchParams.set('primaryColor', props.primaryColor.replace('#', ''));
      }
      
      if (props.secondaryColor) {
        url.searchParams.set('secondaryColor', props.secondaryColor.replace('#', ''));
      }
      
      if (props.themeMode) {
        url.searchParams.set('themeMode', props.themeMode);
      }
      
      url.searchParams.set('analytics', props.analytics.toString());
      
      if (props.debug) {
        url.searchParams.set('debug', 'true');
      }
      
      return url.toString();
    });

    const sizeConfig = {
      small: { maxWidth: '400px', paddingBottom: '177.78%' },
      medium: { maxWidth: '600px', paddingBottom: '133.33%' },
      large: { maxWidth: '800px', paddingBottom: '75%' }
    };

    const containerClass = computed(() => `embed-size-${props.size}`);
    
    const wrapperStyle = computed(() => ({
      position: 'relative',
      paddingBottom: sizeConfig[props.size]?.paddingBottom || '133.33%',
      height: 0,
      overflow: 'hidden'
    }));

    const iframeStyle = {
      position: 'absolute',
      top: 0,
      left: 0,
      width: '100%',
      height: '100%',
      border: 0,
      borderRadius: '8px'
    };

    const embedTitle = computed(() => 
      `Interactive Playbook${props.playbookId ? ` - ${props.playbookId}` : ''}`
    );

    const handleLoad = () => {
      loading.value = false;
      emit('load');
    };

    const handleError = (error) => {
      loading.value = false;
      emit('error', error);
    };

    // Reset loading state when URL changes
    watch(embedUrl, () => {
      loading.value = true;
    });

    return {
      loading,
      embedUrl,
      containerClass,
      wrapperStyle,
      iframeStyle,
      embedTitle,
      handleLoad,
      handleError
    };
  }
};
</script>

<style scoped>
.playbook-embed-container {
  position: relative;
  width: 100%;
  margin: 0 auto;
}

.embed-size-small { max-width: 400px; }
.embed-size-medium { max-width: 600px; }
.embed-size-large { max-width: 800px; }

.embed-loading {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  background: rgba(255, 255, 255, 0.9);
  padding: 20px;
  border-radius: 8px;
}

.loading-spinner {
  text-align: center;
  color: #666;
}

@media (max-width: 600px) {
  .playbook-embed-container {
    max-width: 100% !important;
    padding: 0 16px;
  }
}
</style>

WordPress Integration

Plugin Development

<?php
/**
 * Plugin Name: Plaros Embed
 * Description: Easily embed Plaros interactive playbooks in WordPress
 * Version: 1.0.0
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

class PlarosEmbedPlugin {
    
    public function __construct() {
        add_action('init', array($this, 'init'));
        add_shortcode('plaros_embed', array($this, 'embed_shortcode'));
        add_action('wp_enqueue_scripts', array($this, 'enqueue_styles'));
    }
    
    public function init() {
        // Plugin initialization
    }
    
    public function enqueue_styles() {
        wp_enqueue_style(
            'plaros-embed-styles',
            plugin_dir_url(__FILE__) . 'assets/plaros-embed.css',
            array(),
            '1.0.0'
        );
    }
    
    public function embed_shortcode($atts) {
        $atts = shortcode_atts(array(
            'tenant' => '',
            'zone' => '',
            'playbook' => '',
            'user' => '',
            'width' => '600px',
            'height' => '800px',
            'primary_color' => '',
            'secondary_color' => '',
            'theme_mode' => '',
            'analytics' => 'true',
            'debug' => 'false',
            'class' => ''
        ), $atts, 'plaros_embed');

        // Validate required parameters
        if (empty($atts['tenant']) || empty($atts['zone'])) {
            return '<div class="plaros-embed-error">Error: tenant and zone parameters are required.</div>';
        }

        return $this->generate_embed_html($atts);
    }
    
    private function generate_embed_html($atts) {
        $embed_url = $this->build_embed_url($atts);
        $aspect_ratio = $this->calculate_aspect_ratio($atts['width'], $atts['height']);
        
        $html = sprintf(
            '<div class="plaros-embed-container %s" style="max-width: %s;">
                <div class="plaros-embed-wrapper" style="padding-bottom: %.2f%%;">
                    <iframe 
                        src="%s"
                        class="plaros-embed-iframe"
                        allow="fullscreen"
                        title="Interactive Playbook">
                    </iframe>
                </div>
            </div>',
            esc_attr($atts['class']),
            esc_attr($atts['width']),
            $aspect_ratio,
            esc_url($embed_url)
        );
        
        return $html;
    }
    
    private function build_embed_url($atts) {
        $base_url = sprintf(
            'https://play.plaros.com/v1/embed/%s/playbooks',
            urlencode($atts['tenant'])
        );
        
        $params = array(
            'zonePath' => $atts['zone']
        );
        
        if (!empty($atts['playbook'])) {
            $params['playbookId'] = $atts['playbook'];
        }
        
        if (!empty($atts['user'])) {
            $params['userId'] = $atts['user'];
        }
        
        if (!empty($atts['primary_color'])) {
            $params['primaryColor'] = ltrim($atts['primary_color'], '#');
        }
        
        if (!empty($atts['secondary_color'])) {
            $params['secondaryColor'] = ltrim($atts['secondary_color'], '#');
        }
        
        if (!empty($atts['theme_mode'])) {
            $params['themeMode'] = $atts['theme_mode'];
        }
        
        $params['analytics'] = $atts['analytics'];
        
        if ($atts['debug'] === 'true') {
            $params['debug'] = 'true';
        }
        
        return $base_url . '?' . http_build_query($params);
    }
    
    private function calculate_aspect_ratio($width, $height) {
        $w = (int) filter_var($width, FILTER_SANITIZE_NUMBER_INT);
        $h = (int) filter_var($height, FILTER_SANITIZE_NUMBER_INT);
        
        if ($w > 0 && $h > 0) {
            return ($h / $w) * 100;
        }
        
        return 133.33; // Default 3:4 ratio
    }
}

// Initialize the plugin
new PlarosEmbedPlugin();

// Admin page for configuration
class PlarosEmbedAdmin {
    
    public function __construct() {
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('admin_init', array($this, 'settings_init'));
    }
    
    public function add_admin_menu() {
        add_options_page(
            'Plaros Embed Settings',
            'Plaros Embed',
            'manage_options',
            'plaros_embed',
            array($this, 'options_page')
        );
    }
    
    public function settings_init() {
        register_setting('plaros_embed', 'plaros_embed_settings');
        
        add_settings_section(
            'plaros_embed_section',
            'Default Configuration',
            array($this, 'settings_section_callback'),
            'plaros_embed'
        );
        
        add_settings_field(
            'default_tenant',
            'Default Tenant Slug',
            array($this, 'text_field_callback'),
            'plaros_embed',
            'plaros_embed_section',
            array('field' => 'default_tenant')
        );
    }
    
    public function options_page() {
        ?>
        <div class="wrap">
            <h1>Plaros Embed Settings</h1>
            <form action="options.php" method="post">
                <?php
                settings_fields('plaros_embed');
                do_settings_sections('plaros_embed');
                submit_button();
                ?>
            </form>
            
            <div class="plaros-usage-examples">
                <h2>Usage Examples</h2>
                <h3>Basic Embed</h3>
                <code>[plaros_embed tenant="your-tenant" zone="your-zone"]</code>
                
                <h3>With Custom Styling</h3>
                <code>[plaros_embed tenant="your-tenant" zone="your-zone" primary_color="#FF6B35" theme_mode="dark"]</code>
                
                <h3>Specific Playbook</h3>
                <code>[plaros_embed tenant="your-tenant" zone="your-zone" playbook="pb_123456" user="current_user"]</code>
            </div>
        </div>
        <?php
    }
    
    public function settings_section_callback() {
        echo 'Configure default settings for Plaros embeds.';
    }
    
    public function text_field_callback($args) {
        $options = get_option('plaros_embed_settings');
        $value = isset($options[$args['field']]) ? $options[$args['field']] : '';
        
        printf(
            '<input type="text" id="%s" name="plaros_embed_settings[%s]" value="%s" class="regular-text" />',
            $args['field'],
            $args['field'],
            esc_attr($value)
        );
    }
}

if (is_admin()) {
    new PlarosEmbedAdmin();
}
?>

WordPress CSS File

/* assets/plaros-embed.css */
.plaros-embed-container {
  position: relative;
  width: 100%;
  margin: 20px auto;
  background: #f9f9f9;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}

.plaros-embed-wrapper {
  position: relative;
  height: 0;
  overflow: hidden;
}

.plaros-embed-iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

.plaros-embed-error {
  background: #fee;
  border: 1px solid #fcc;
  border-radius: 4px;
  padding: 15px;
  margin: 20px 0;
  color: #c33;
  text-align: center;
}

/* Responsive design */
@media (max-width: 768px) {
  .plaros-embed-container {
    margin: 15px 0;
    border-radius: 8px;
  }
}

/* Theme integration */
.wp-block-group .plaros-embed-container {
  background: var(--wp--preset--color--background, #ffffff);
}

.has-dark-background .plaros-embed-container {
  background: var(--wp--preset--color--foreground, #000000);
  box-shadow: 0 4px 20px rgba(255, 255, 255, 0.1);
}

Learning Management System (LMS)

Moodle Integration

<?php
// mod/plarosembed/lib.php

function plarosembed_add_instance($plarosembed) {
    global $DB;
    
    $plarosembed->timecreated = time();
    $plarosembed->timemodified = time();
    
    return $DB->insert_record('plarosembed', $plarosembed);
}

function plarosembed_view($plarosembed, $course, $cm, $context) {
    global $PAGE, $OUTPUT;
    
    $PAGE->set_url('/mod/plarosembed/view.php', array('id' => $cm->id));
    $PAGE->set_title($plarosembed->name);
    $PAGE->set_heading($course->fullname);
    
    echo $OUTPUT->header();
    echo $OUTPUT->heading($plarosembed->name);
    
    // Generate embed HTML
    $embed_url = build_plaros_embed_url($plarosembed);
    
    echo html_writer::start_div('plaros-embed-container');
    echo html_writer::start_div('plaros-embed-wrapper');
    echo html_writer::empty_tag('iframe', array(
        'src' => $embed_url,
        'class' => 'plaros-embed-iframe',
        'allow' => 'fullscreen',
        'title' => 'Interactive Learning Module'
    ));
    echo html_writer::end_div();
    echo html_writer::end_div();
    
    echo $OUTPUT->footer();
}

function build_plaros_embed_url($plarosembed) {
    global $USER;
    
    $base_url = "https://play.plaros.com/v1/embed/{$plarosembed->tenant_slug}/playbooks";
    
    $params = array(
        'zonePath' => $plarosembed->zone_path,
        'userId' => $USER->id,
        'analytics' => 'true'
    );
    
    if (!empty($plarosembed->playbook_id)) {
        $params['playbookId'] = $plarosembed->playbook_id;
    }
    
    return $base_url . '?' . http_build_query($params);
}
?>

Canvas LTI Integration

// Canvas LTI Tool for Plaros Embeds
class PlarosLTITool {
  constructor(config) {
    this.config = config;
    this.initializeTool();
  }
  
  initializeTool() {
    // LTI launch handling
    document.addEventListener('DOMContentLoaded', () => {
      this.renderEmbed();
    });
  }
  
  renderEmbed() {
    const container = document.getElementById('plaros-embed-container');
    if (!container) return;
    
    const embedUrl = this.buildEmbedUrl();
    
    container.innerHTML = `
      <div class="lti-embed-wrapper" style="
        position: relative;
        padding-bottom: 75%;
        height: 0;
        overflow: hidden;
        background: #f5f5f5;
        border-radius: 8px;
      ">
        <iframe
          src="${embedUrl}"
          style="
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            border: 0;
          "
          allow="fullscreen"
          title="Interactive Learning Module">
        </iframe>
      </div>
    `;
  }
  
  buildEmbedUrl() {
    const url = new URL(`https://play.plaros.com/v1/embed/${this.config.tenantSlug}/playbooks`);
    
    url.searchParams.set('zonePath', this.config.zonePath);
    url.searchParams.set('userId', this.config.userId);
    url.searchParams.set('analytics', 'true');
    
    if (this.config.playbookId) {
      url.searchParams.set('playbookId', this.config.playbookId);
    }
    
    return url.toString();
  }
  
  // Grade passback integration
  async sendGrade(score, maxScore = 100) {
    const grade = (score / maxScore) * 1.0; // LTI expects 0-1 range
    
    try {
      await fetch('/lti/grade-passback', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          userId: this.config.userId,
          grade: grade,
          activityId: this.config.activityId
        })
      });
    } catch (error) {
      console.error('Grade passback failed:', error);
    }
  }
}

// Initialize tool
const ltiTool = new PlarosLTITool({
  tenantSlug: LTI_CONFIG.tenant_slug,
  zonePath: LTI_CONFIG.zone_path,
  playbookId: LTI_CONFIG.playbook_id,
  userId: LTI_CONFIG.user_id,
  activityId: LTI_CONFIG.activity_id
});

Next Steps

  • Learn about Custom Theming to style your embeds

  • Check Configuration Options for parameter details

  • See Troubleshooting for common integration issues