Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add URL change detection and reporting for embedded iframes
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.
  • Loading branch information
enisn committed Jun 27, 2025
commit a63fa305e2b62022b27423c108194e04147d7374
91 changes: 91 additions & 0 deletions npm/packs/aspnetcore.mvc.ui.embedding/src/abp-embedding-iframe.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,28 @@
this.observer = null;
this.debounceTimer = null;
this.lastReportedHeight = 0;
this.lastReportedUrl = null;

// Bind methods to ensure correct 'this' context
this.measureHeight = this.measureHeight.bind(this);
this.reportHeight = this.reportHeight.bind(this);
this.debouncedReportHeight = this.debouncedReportHeight.bind(this);
this.onContentChange = this.onContentChange.bind(this);
this.reportUrlChange = this.reportUrlChange.bind(this);
this.handlePopState = this.handlePopState.bind(this);
this.handleHashChange = this.handleHashChange.bind(this);
}

init() {
// Auto-enable for iframe context
this.enable();

// Report initial URL
this.reportUrlChange();

// Setup URL change monitoring
this.setupUrlMonitoring();

// Wait for DOM to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
Expand Down Expand Up @@ -108,6 +118,10 @@
}

window.removeEventListener('resize', this.debouncedReportHeight);

// Remove URL monitoring listeners
window.removeEventListener('popstate', this.handlePopState);
window.removeEventListener('hashchange', this.handleHashChange);
}

setupMonitoring() {
Expand Down Expand Up @@ -228,6 +242,83 @@
this.debounceTimer = null;
}, this.config.debounceDelay);
}

setupUrlMonitoring() {
// Monitor popstate events (back/forward navigation)
window.addEventListener('popstate', this.handlePopState);

// Monitor hashchange events
window.addEventListener('hashchange', this.handleHashChange);

// Override history methods to catch programmatic navigation
this.overrideHistoryMethods();

// Monitor for navigation through other means
this.setupNavigationMonitoring();
}

overrideHistoryMethods() {
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;

history.pushState = (...args) => {
const result = originalPushState.apply(history, args);
setTimeout(() => this.reportUrlChange(), 0);
return result;
};

history.replaceState = (...args) => {
const result = originalReplaceState.apply(history, args);
setTimeout(() => this.reportUrlChange(), 0);
return result;
};
}

setupNavigationMonitoring() {
// Monitor for clicks on links
document.addEventListener('click', (event) => {
const link = event.target.closest('a');
if (link && link.href) {
// Delay to allow navigation to complete
setTimeout(() => this.reportUrlChange(), 100);
}
});

// Monitor for form submissions
document.addEventListener('submit', () => {
setTimeout(() => this.reportUrlChange(), 100);
});
}

handlePopState(event) {
this.reportUrlChange();
}

handleHashChange(event) {
this.reportUrlChange();
}

reportUrlChange() {
try {
const currentUrl = window.location.href;

// Only report if URL actually changed
if (currentUrl !== this.lastReportedUrl) {
this.lastReportedUrl = currentUrl;

// Send URL change to parent
if (window.parent && window.parent !== window) {
window.parent.postMessage({
type: 'url-change',
url: currentUrl,
timestamp: Date.now()
}, '*');
}
}
} catch (e) {
console.warn('ABP Embedding Iframe: Failed to report URL change', e);
}
}
}

// Create and initialize the handler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ abp-embedding iframe {
-webkit-text-shadow: none !important;
-moz-text-shadow: none !important;
width: 100%;
min-height: 50vh; /* Minimum height for better UX */
}

/* Responsive behavior */
Expand Down
64 changes: 39 additions & 25 deletions npm/packs/aspnetcore.mvc.ui.embedding/src/abp-embedding.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,23 @@
}
}

ensureInitialHistoryEntry() {
if (!this.isHistoryManager) return;

// Only push initial state if there's no fragment currently
if (!window.location.hash || !window.location.hash.startsWith('#page=')) {
// Replace current state to represent the initial iframe state
history.replaceState({ isInitial: true }, '', window.location.href);
}
}

updateUrlFragment(relativePath) {
if (!this.isHistoryManager || !relativePath) return;

const newHash = '#page=' + relativePath;
if (window.location.hash !== newHash) {
// Update URL without page refresh
history.pushState({}, '', window.location.pathname + window.location.search + newHash);
history.pushState({ relativePath: relativePath }, '', window.location.pathname + window.location.search + newHash);
}
}

Expand All @@ -149,9 +159,13 @@
if (fragment && this.initialSrc) {
const absoluteUrl = this.buildAbsoluteUrl(fragment);
this.iframe.src = absoluteUrl;
} else if (!fragment && this.initialSrc) {
} else if (!fragment) {
// No fragment, navigate back to initial URL
this.iframe.src = this.initialSrc;
if (this.initialUrl) {
this.iframe.src = this.initialUrl;
} else if (this.initialSrc) {
this.iframe.src = this.initialSrc;
}
}
}

Expand Down Expand Up @@ -244,12 +258,30 @@
handleIframeLoad() {
this.isIframeLoaded = true;

// Dispatch custom event
this.dispatchEvent(new CustomEvent('iframe-loaded', {
detail: { iframe: this.iframe },
bubbles: true
}));
}

handleIframeMessage(event) {
// Simple message filtering - only accept messages from our iframe
if (this.iframe && event.source === this.iframe.contentWindow && event.data) {
if (event.data.type === 'height-update') {
this.updateIframeHeight(event.data.height);
} else if (event.data.type === 'url-change') {
this.handleUrlChange(event.data.url);
}
}
}

handleUrlChange(currentUrl) {
try {
const currentUrl = this.iframe.contentWindow.location.href;

if (!this.initialUrl) {
// First load - store the initial URL
// First load - store the initial URL and ensure history entry exists
this.initialUrl = currentUrl;
this.ensureInitialHistoryEntry();
} else if (this.isHistoryManager && currentUrl !== this.initialUrl) {
// Navigation detected - check if it's same domain
if (currentUrl.startsWith(this.initialUrl) ||
Expand All @@ -263,25 +295,7 @@
}
}
} catch (e) {
// Handle potential cross-origin errors gracefully
console.warn('ABP Embedding: Could not access iframe URL due to cross-origin restrictions', e);
}

// Dispatch custom event
this.dispatchEvent(new CustomEvent('iframe-loaded', {
detail: { iframe: this.iframe },
bubbles: true
}));
}

handleIframeMessage(event) {
// Simple message filtering - only accept height updates from our iframe
if (this.iframe &&
event.source === this.iframe.contentWindow &&
event.data &&
event.data.type === 'height-update') {

this.updateIframeHeight(event.data.height);
console.warn('ABP Embedding: Failed to handle URL change', e);
}
}

Expand Down