Widget Integration Patterns
This guide demonstrates common patterns for integrating multiple AlphaSense widgets to create powerful, interconnected user experiences.
widget documentation:
- Generative Search Integration - Citation handling and conversation management
- Document Viewer Integration - Basic document selection patterns
Then return here for advanced multi-widget layouts and best practices. :::
Document Discovery and Viewing
The most common integration pattern combines widgets that help users discover documents with those that display them.
Generative Search + Document Viewer
Create an AI-powered research interface where users can ask questions and immediately view cited sources:
// Container layout (side-by-side)
// <div style="display: flex; height: 600px;">
//   <div id="generative-search" style="width: 40%; margin-right: 10px;"></div>
//   <div id="document-viewer" style="width: 60%;"></div>
// </div>
let documentViewer = new AlphaSenseWidget({
  target: '#document-viewer',
  widgetType: 'documentViewer',
  documentViewerParams: {
    docId: 'welcome-document-id', // Initial welcome document
  },
  width: '100%',
  height: '100%',
}).init()
const generativeSearch = new AlphaSenseWidget({
  target: '#generative-search',
  widgetType: 'generativeSearch',
  generativeSearchParams: {
    initialPrompt: 'What are the latest trends in renewable energy?',
  },
  onDocumentClick: docId => {
    updateDocumentViewer(docId)
  },
  width: '100%',
  height: '100%',
}).init()
function updateDocumentViewer(docId) {
  documentViewer.destroy()
  documentViewer = new AlphaSenseWidget({
    target: '#document-viewer',
    widgetType: 'documentViewer',
    documentViewerParams: {docId},
    width: '100%',
    height: '100%',
  }).init()
}
Document List + Document Viewer
Traditional document browsing experience with list and preview:
// Container layout (side-by-side)
// <div style="display: flex; height: 600px;">
//   <div id="document-list" style="width: 35%; margin-right: 10px;"></div>
//   <div id="document-viewer" style="width: 65%;"></div>
// </div>
let documentViewer = new AlphaSenseWidget({
  target: '#document-viewer',
  widgetType: 'documentViewer',
  documentViewerParams: {
    docId: 'initial-document-id',
  },
  width: '100%',
  height: '100%',
}).init()
const documentList = new AlphaSenseWidget({
  target: '#document-list',
  widgetType: 'documentList',
  companyParams: {
    tickerCode: 'AAPL',
  },
  onDocumentClick: docId => {
    updateDocumentViewer(docId)
  },
  width: '100%',
  height: '100%',
}).init()
function updateDocumentViewer(docId) {
  documentViewer.destroy()
  documentViewer = new AlphaSenseWidget({
    target: '#document-viewer',
    widgetType: 'documentViewer',
    documentViewerParams: {docId},
    width: '100%',
    height: '100%',
  }).init()
}
Three-Panel Research Interface
Combine multiple discovery methods with document viewing for comprehensive research:
// Container layout (three columns)
// <div style="display: flex; height: 600px;">
//   <div id="document-list" style="width: 25%; margin-right: 10px;"></div>
//   <div id="generative-search" style="width: 35%; margin-right: 10px;"></div>
//   <div id="document-viewer" style="width: 40%;"></div>
// </div>
let documentViewer = new AlphaSenseWidget({
  target: '#document-viewer',
  widgetType: 'documentViewer',
  documentViewerParams: {
    docId: 'initial-document-id',
  },
  width: '100%',
  height: '100%',
}).init()
// Document List for browsing
const documentList = new AlphaSenseWidget({
  target: '#document-list',
  widgetType: 'documentList',
  companyParams: {
    tickerCode: 'AAPL',
  },
  onDocumentClick: docId => {
    updateDocumentViewer(docId, 'Document List')
  },
  width: '100%',
  height: '100%',
}).init()
// Generative Search for AI-powered discovery
const generativeSearch = new AlphaSenseWidget({
  target: '#generative-search',
  widgetType: 'generativeSearch',
  onDocumentClick: docId => {
    updateDocumentViewer(docId, 'Generative Search')
  },
  width: '100%',
  height: '100%',
}).init()
function updateDocumentViewer(docId, source) {
  console.log(`Document selected from ${source}:`, docId)
  documentViewer.destroy()
  documentViewer = new AlphaSenseWidget({
    target: '#document-viewer',
    widgetType: 'documentViewer',
    documentViewerParams: {docId},
    width: '100%',
    height: '100%',
  }).init()
}
Tabbed Interface Pattern
Organize different widget types in tabs for better space utilization:
<!-- Tab navigation -->
<div class="tab-navigation">
  <button class="tab-btn active" onclick="showTab('search')">AI Search</button>
  <button class="tab-btn" onclick="showTab('browse')">Browse Documents</button>
  <button class="tab-btn" onclick="showTab('company')">Company Info</button>
</div>
<!-- Tab content containers -->
<div style="height: 600px;">
  <div id="search-tab" class="tab-content active" style="height: 100%;">
    <div style="display: flex; height: 100%;">
      <div id="generative-search" style="width: 40%; margin-right: 10px;"></div>
      <div id="document-viewer-search" style="width: 60%;"></div>
    </div>
  </div>
  <div id="browse-tab" class="tab-content" style="height: 100%; display: none;">
    <div style="display: flex; height: 100%;">
      <div id="document-list" style="width: 40%; margin-right: 10px;"></div>
      <div id="document-viewer-browse" style="width: 60%;"></div>
    </div>
  </div>
  <div id="company-tab" class="tab-content" style="height: 100%; display: none;">
    <div id="company-summary" style="height: 100%;"></div>
  </div>
</div>
let activeTab = 'search'
let widgets = {}
// Initialize all widgets
function initializeWidgets() {
  // Search tab widgets
  widgets.documentViewerSearch = new AlphaSenseWidget({
    target: '#document-viewer-search',
    widgetType: 'documentViewer',
    documentViewerParams: {docId: 'initial-doc-id'},
    width: '100%',
    height: '100%',
  }).init()
  widgets.generativeSearch = new AlphaSenseWidget({
    target: '#generative-search',
    widgetType: 'generativeSearch',
    onDocumentClick: docId => {
      updateDocumentViewer('search', docId)
    },
    width: '100%',
    height: '100%',
  }).init()
  // Browse tab widgets (initialize when first shown)
  // Company tab widgets (initialize when first shown)
}
function showTab(tabName) {
  // Hide all tabs
  document.querySelectorAll('.tab-content').forEach(tab => {
    tab.style.display = 'none'
  })
  document.querySelectorAll('.tab-btn').forEach(btn => {
    btn.classList.remove('active')
  })
  // Show selected tab
  document.getElementById(`${tabName}-tab`).style.display = 'block'
  event.target.classList.add('active')
  // Initialize widgets for tab if not already done
  if (tabName === 'browse' && !widgets.documentList) {
    widgets.documentViewerBrowse = new AlphaSenseWidget({
      target: '#document-viewer-browse',
      widgetType: 'documentViewer',
      documentViewerParams: {docId: 'initial-doc-id'},
      width: '100%',
      height: '100%',
    }).init()
    widgets.documentList = new AlphaSenseWidget({
      target: '#document-list',
      widgetType: 'documentList',
      companyParams: {tickerCode: 'AAPL'},
      onDocumentClick: docId => {
        updateDocumentViewer('browse', docId)
      },
      width: '100%',
      height: '100%',
    }).init()
  }
  if (tabName === 'company' && !widgets.companySummary) {
    widgets.companySummary = new AlphaSenseWidget({
      target: '#company-summary',
      widgetType: 'companySummary',
      companyParams: {tickerCode: 'AAPL'},
      width: '100%',
      height: '100%',
    }).init()
  }
  activeTab = tabName
}
function updateDocumentViewer(tab, docId) {
  const viewerWidget = widgets[`documentViewer${tab.charAt(0).toUpperCase() + tab.slice(1)}`]
  if (viewerWidget) {
    viewerWidget.destroy()
    widgets[`documentViewer${tab.charAt(0).toUpperCase() + tab.slice(1)}`] = new AlphaSenseWidget({
      target: `#document-viewer-${tab}`,
      widgetType: 'documentViewer',
      documentViewerParams: {docId},
      width: '100%',
      height: '100%',
    }).init()
  }
}
// Initialize on page load
initializeWidgets()
Modal/Popup Document Viewer
Open documents in modal overlays while keeping the main interface intact:
const documentList = new AlphaSenseWidget({
  target: '#document-list',
  widgetType: 'documentList',
  companyParams: {
    tickerCode: 'AAPL',
  },
  onDocumentClick: docId => {
    openDocumentModal(docId)
  },
  width: '100%',
  height: '500px',
}).init()
function openDocumentModal(docId) {
  // Create modal container
  const modal = document.createElement('div')
  modal.style.cssText = `
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.8);
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
  `
  const modalContent = document.createElement('div')
  modalContent.style.cssText = `
    width: 90%;
    height: 90%;
    background: white;
    border-radius: 8px;
    position: relative;
  `
  const closeButton = document.createElement('button')
  closeButton.textContent = '×'
  closeButton.style.cssText = `
    position: absolute;
    top: 10px;
    right: 15px;
    background: none;
    border: none;
    font-size: 24px;
    cursor: pointer;
    z-index: 1001;
  `
  const viewerContainer = document.createElement('div')
  viewerContainer.id = 'modal-document-viewer'
  viewerContainer.style.cssText = `
    width: 100%;
    height: 100%;
    border-radius: 8px;
  `
  modalContent.appendChild(closeButton)
  modalContent.appendChild(viewerContainer)
  modal.appendChild(modalContent)
  document.body.appendChild(modal)
  // Initialize document viewer in modal
  const modalViewer = new AlphaSenseWidget({
    target: '#modal-document-viewer',
    widgetType: 'documentViewer',
    documentViewerParams: {docId},
    width: '100%',
    height: '100%',
  }).init()
  // Close modal functionality
  const closeModal = () => {
    modalViewer.destroy()
    document.body.removeChild(modal)
  }
  closeButton.addEventListener('click', closeModal)
  modal.addEventListener('click', e => {
    if (e.target === modal) closeModal()
  })
  // ESC key to close
  const handleEscape = e => {
    if (e.key === 'Escape') {
      closeModal()
      document.removeEventListener('keydown', handleEscape)
    }
  }
  document.addEventListener('keydown', handleEscape)
}
Best Practices for Integration
Memory Management
class WidgetManager {
  constructor() {
    this.widgets = new Map()
  }
  createWidget(id, config) {
    // Destroy existing widget if it exists
    if (this.widgets.has(id)) {
      this.widgets.get(id).destroy()
    }
    const widget = new AlphaSenseWidget(config).init()
    this.widgets.set(id, widget)
    return widget
  }
  destroyWidget(id) {
    if (this.widgets.has(id)) {
      this.widgets.get(id).destroy()
      this.widgets.delete(id)
    }
  }
  destroyAll() {
    this.widgets.forEach(widget => widget.destroy())
    this.widgets.clear()
  }
}
const widgetManager = new WidgetManager()
// Use throughout your application
const documentViewer = widgetManager.createWidget('main-viewer', {
  target: '#document-viewer',
  widgetType: 'documentViewer',
  documentViewerParams: {docId: 'doc-123'},
})
Error Handling
function createWidgetWithErrorHandling(config, containerId) {
  try {
    return new AlphaSenseWidget(config).init()
  } catch (error) {
    console.error(`Failed to initialize widget in ${containerId}:`, error)
    // Show error state in container
    document.getElementById(containerId.replace('#', '')).innerHTML = `
      <div class="widget-error">
        <h4>Unable to load widget</h4>
        <p>${error.message}</p>
        <button onclick="retryWidget('${containerId}')">Retry</button>
      </div>
    `
    return null
  }
}
function retryWidget(containerId) {
  // Clear error state and retry initialization
  document.getElementById(containerId.replace('#', '')).innerHTML =
    '<div class="loading">Loading widget...</div>'
  // Retry with original configuration
  // Implementation depends on your specific setup
}
Responsive Design
function createResponsiveLayout() {
  const isMobile = window.innerWidth < 768
  if (isMobile) {
    // Stack widgets vertically on mobile
    return {
      searchConfig: {
        target: '#search-container',
        widgetType: 'generativeSearch',
        width: '100%',
        height: '300px',
      },
      viewerConfig: {
        target: '#viewer-container',
        widgetType: 'documentViewer',
        documentViewerParams: {docId: 'initial-doc'},
        width: '100%',
        height: '400px',
      },
    }
  } else {
    // Side-by-side on desktop
    return {
      searchConfig: {
        target: '#search-container',
        widgetType: 'generativeSearch',
        width: '100%',
        height: '600px',
      },
      viewerConfig: {
        target: '#viewer-container',
        widgetType: 'documentViewer',
        documentViewerParams: {docId: 'initial-doc'},
        width: '100%',
        height: '600px',
      },
    }
  }
}
// Initialize with responsive configuration
const configs = createResponsiveLayout()
const searchWidget = new AlphaSenseWidget(configs.searchConfig).init()
const viewerWidget = new AlphaSenseWidget(configs.viewerConfig).init()
// Re-initialize on window resize
window.addEventListener(
  'resize',
  debounce(() => {
    const newConfigs = createResponsiveLayout()
    // Recreate widgets with new configurations
  }, 300),
)
These integration patterns provide flexible ways to combine AlphaSense widgets for different use cases, from simple document viewing to complex research interfaces.