Skip to content

Page Embedding Feature (aka Hybrid UI) #23161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: dev
Choose a base branch
from
Draft

Page Embedding Feature (aka Hybrid UI) #23161

wants to merge 14 commits into from

Conversation

enisn
Copy link
Member

@enisn enisn commented Jun 23, 2025

Description

Resolves #23102

This PR adds a couple of features. Here is the list and explaination:

Controlling LayoutElements via IPageLayout

@using Volo.Abp.AspNetCore.Mvc.UI.Layout
@inject IPageLayout PageLayout
@{
    PageLayout.RenderLayoutElements = false;
}
  • It's ready for embedding:

image


Embedding Library

Introduced a new package named: Volo.Abp.AspNetCore.Mvc.UI.Embedding.

When this package added, it replaces the original PageLayout with EmbeddingPageLayout and controls layout elements according to options:

Configure<PageEmbeddingOptions>(options =>
{
    // Automatically embed pages when accessed from iframes
    options.AlwaysEmbedIFrameRequests = true;
});
Configure<PageEmbeddingOptions>(options =>
{
    // Specific paths that should always be embedded
    options.EmbeddedPaths.Add("/embed/dashboard");
    options.EmbeddedPaths.Add("/reports/widget");
    
    // Path patterns with wildcards
    options.EmbeddedPathPatterns.Add("/api/embed/*");
    options.EmbeddedPathPatterns.Add("*/widget");
    
    // Customize query parameter name and values
    options.QueryParameterName = "iframe";
    options.QueryParameterValues.Add("yes");
    
    // Enable automatic iframe detection
    options.AlwaysEmbedIFrameRequests = true;
});

How It Works

  1. The PageLayout.RenderLayoutElements property is automatically set to false when embedding conditions are met
  2. Themes check this property to conditionally render navigation, headers, footers, etc.
  3. The PageEmbeddingService evaluates multiple factors:
    • IFrame detection (when AlwaysEmbedIFrameRequests = true):
      • Sec-Fetch-Dest: iframe header (most reliable)
      • Sec-Fetch-Site + Sec-Fetch-Mode headers
      • Custom headers (X-Frame-Request, X-Iframe-Request, etc.)
      • X-Requested-With header
      • Referer header analysis (least reliable)
    • Query parameters (?embed=true)
    • Configured embedded paths
    • Path patterns with wildcards

ABP Embedding Web Component (npm package)

A framework-agnostic web component for embedding iframe content with data passing capabilities.

Features

  • Framework Agnostic: Works with plain HTML, Vue, Angular, React, and any other framework
  • Auto-Height: Automatically resize iframe to match content height
  • Data Passing: Send data to iframe content using postMessage API
  • Event Handling: Listen for iframe load and message events
  • URL Synchronization: Keep parent and iframe URLs in sync with browser history support
  • Clean Styling: No borders, outlines, or visual artifacts by default
  • Responsive: Built-in responsive behavior options
  • TypeScript Ready: Includes type definitions
  • Accessible: Proper ARIA attributes and semantic HTML

Installation

npm install @abp/aspnetcore.mvc.ui.embedding

Usage

Basic HTML Usage

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="~/libs/abp/embedding/abp-embedding.css">
</head>
<body>
    <!-- Basic usage -->
    <abp-embedding src="https://example.com" width="800" height="600"></abp-embedding>

    <!-- With responsive behavior -->
    <abp-embedding src="https://example.com" class="responsive"></abp-embedding>

    <!-- With URL synchronization -->
    <abp-embedding src="https://example.com" width="100%" height="600px" url-sync="true"></abp-embedding>

    <!-- With auto-height -->
    <abp-embedding src="https://example.com" width="100%" auto-height="true"></abp-embedding>

    <script src="~/libs/abp/embedding/abp-embedding.js"></script>
</body>
</html>

Iframe Content Setup

Include this script in your iframe content to enable height communication:

<!DOCTYPE html>
<html>
<head>
    <title>Your Iframe Content</title>
</head>
<body>
    <!-- Your content here -->
    
    <script src="path/to/abp-embedding-iframe.js"></script>
</body>
</html>

The iframe script automatically:

  • Waits for page to fully load (including images)
  • Measures content height using multiple methods
  • Sends height updates to parent via postMessage
  • Monitors for content changes (if enabled)
  • Handles configuration from parent

Auto-Height Events

// Listen for height updates
embedding.addEventListener('height-updated', (event) => {
    const { originalHeight, appliedHeight, wasConstrained } = event.detail;
    console.log(`Height: ${originalHeight}px → ${appliedHeight}px`);
    
    if (wasConstrained) {
        console.log('Height was constrained by min/max limits');
    }
});

Configuration Options

{
    minHeight: 100,        // Minimum iframe height (default: 100)
    maxHeight: 10000,      // Maximum iframe height (default: 90% of viewport)
    watchForChanges: true, // Monitor DOM changes (default: true)
    debounceDelay: 250     // Debounce delay for change detection (default: 250ms)
}

URL Synchronization

The web component supports URL synchronization between the parent page and iframe content. When enabled, the component will:

  • Keep parent and iframe URLs synchronized (paths only)
  • Add ?embed=1 parameter to all iframe URLs
  • Support browser back/forward navigation
  • Work seamlessly with SPAs (Angular, React, Vue, etc.)

Example URL Mapping

Parent URL:  https://parent.com/forms/2
Iframe URL:  https://iframe.com/forms/2?embed=1

Enable URL Sync

<abp-embedding 
    src="https://your-spa.com" 
    url-sync="true"
    width="100%" 
    height="600px">
</abp-embedding>

Iframe Implementation for URL Sync

Your iframe application needs to implement URL change notifications:

// Detect embedded mode
const isEmbedded = new URLSearchParams(window.location.search).has('embed');

// Your SPA router
function navigateToPath(path) {
    // Update your SPA content
    updateContent(path);
    
    // Notify parent if embedded
    if (isEmbedded) {
        window.parent.postMessage({
            type: 'url-change',
            path: path
        }, '*');
    }
}

// Listen for navigation commands from parent
window.addEventListener('message', function(event) {
    if (event.data && event.data.type === 'navigate') {
        // Update URL without notifying parent (to avoid loop)
        window.history.replaceState({}, '', event.data.path + '?embed=1');
        
        // Navigate in your SPA
        navigateToPath(event.data.path);
    }
});

URL Sync Events

embedding.addEventListener('url-synced', (event) => {
    const { type, oldPath, newPath, path, url } = event.detail;
    
    if (type === 'iframe-to-parent') {
        console.log(`URL synced from iframe: ${oldPath}${newPath}`);
    } else if (type === 'parent-to-iframe') {
        console.log(`URL synced to iframe: ${path}`);
    }
});

enisn added 2 commits June 23, 2025 16:03
Introduces a RenderLayout property to IPageLayout and PageLayout, allowing conditional rendering of the menu, toolbar, and footer in the Application layout. Updates Application.cshtml to respect this property, enabling more flexible page layouts.
Introduces the RenderLayoutElements property to IPageLayout and updates usage throughout the codebase. This allows pages to control whether navigation, toolbar, and other layout elements are rendered, providing more flexibility for special pages like error or embedded content. Documentation is updated to explain the new property and its usage.
@enisn enisn requested a review from Copilot June 23, 2025 13:44
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Adds the ability to toggle the rendering of navigation, toolbar, and other surrounding layout elements in MVC Razor views via a new RenderLayoutElements flag on IPageLayout.

  • Introduces RenderLayoutElements property on IPageLayout and PageLayout (defaulting to true).
  • Updates Application.cshtml and Account.cshtml to check PageLayout.RenderLayoutElements before rendering the main navbar and to adjust container width.
  • Extends documentation to cover the new RenderLayoutElements setting.

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml Conditionally render navbar and adjust containerClass based on new flag
modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Account.cshtml Inject IPageLayout and wrap navbar in conditional block
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/PageLayout.cs Added RenderLayoutElements auto-property and initializer for Content
framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/IPageLayout.cs Added RenderLayoutElements to interface
docs/en/framework/ui/mvc-razor-pages/page-header.md Documented RenderLayoutElements usage
Comments suppressed due to low confidence (3)

framework/src/Volo.Abp.AspNetCore.Mvc.UI/Volo/Abp/AspNetCore/Mvc/UI/Layout/PageLayout.cs:9

  • No unit or integration tests were added to verify that toggling RenderLayoutElements properly hides or shows layout elements. Consider adding tests around this new behavior.
    public bool RenderLayoutElements { get; set; } = true;

docs/en/framework/ui/mvc-razor-pages/page-header.md:22

  • Duplicate word pages in the description. It should read: "useful for public pages, error pages, or embedded content..."
* This is useful for pages public pages, error pages, or embedded content where you want a clean layout without navigation.

modules/basic-theme/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts/Application.cshtml:21

  • PageLayout is referenced here but not injected in this view. Add @inject IPageLayout PageLayout at the top of Application.cshtml to prevent a runtime error.
    var containerClass = ViewBag.FluidLayout == true || PageLayout.RenderLayoutElements == false ? "container-fluid" : "container"; //TODO: Better and type-safe options

@enisn enisn added this to the 10.0-preview milestone Jun 24, 2025
enisn added 2 commits June 25, 2025 11:15
Introduces the PageEmbedding feature, allowing ABP MVC pages to be embedded in other UI technologies like iframes by conditionally disabling layout elements. This includes new classes and interfaces for embedding services, options for configuration, and automatic iframe detection. Updates to the PageLayout class ensure compatibility with the new embedding logic. Documentation is added to explain usage and configuration options.
@enisn enisn changed the title Controlling layout elements rendering by IPageLayout on MVC Page Embedding Feature (aka Hybrid UI) Jun 25, 2025
Included 'Volo.Abp.AspNetCore.Mvc.UI.Embedding' in the list of projects for packaging. This ensures the Embedding module is built and packaged with the rest of the framework.
@enisn enisn requested a review from hikalkan June 25, 2025 08:20
enisn added 9 commits June 25, 2025 13:41
Updated the CSS for the abp-embedding component to improve iframe presentation. Added important flags to various properties to ensure consistent rendering across browsers, including border, outline, background, and shadow styles. This change aims to enhance the visual integration of embedded content.
Introduces an auto-height capability to the aspnetcore.mvc.ui.embedding web component, allowing iframes to automatically resize to fit their content. Adds supporting documentation, example demos, a new iframe-side script for height measurement and communication, and updates CSS and JS to support configuration, events, and programmatic control of auto-height.
Introduced EmbeddedScriptViewComponent and its view to inject an embedding script when the page is embedded. Updated EmbeddingPageLayout to expose IsEmbedded property and set it based on the request. Registered the view component to render at the end of the body via layout hooks.
Simplifies the ABP embedding iframe script and web component by removing bidirectional messaging, URL sync, and complex height measurement strategies. The iframe now only reports its height to the parent, and the parent component updates the iframe height accordingly. CSS is updated to remove default and auto-height-related height rules, delegating all height management to JavaScript for more predictable behavior.
Introduces logic to synchronize the browser's URL fragment with the navigation state of the embedded iframe, allowing back/forward navigation and deep linking. Only one instance manages history to prevent conflicts, and cross-origin navigation is handled gracefully.
Introduces URL change monitoring in abp-embedding-iframe.js, reporting navigation events to the parent window via postMessage. Updates abp-embedding.js to handle 'url-change' messages from the iframe and dispatches a custom 'iframe-loaded' event. Also sets a minimum height for embedded iframes in abp-embedding.css to improve user experience.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ABP Hybrid UI System: Re-using module UIs in different technologies
1 participant