Browse Source

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.
hybrid-ui
enisn 7 months ago
parent
commit
a63fa305e2
No known key found for this signature in database GPG Key ID: A052619F04155D1C
  1. 91
      npm/packs/aspnetcore.mvc.ui.embedding/src/abp-embedding-iframe.js
  2. 1
      npm/packs/aspnetcore.mvc.ui.embedding/src/abp-embedding.css
  3. 64
      npm/packs/aspnetcore.mvc.ui.embedding/src/abp-embedding.js

91
npm/packs/aspnetcore.mvc.ui.embedding/src/abp-embedding-iframe.js

@ -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', () => {
@ -108,6 +118,10 @@
}
window.removeEventListener('resize', this.debouncedReportHeight);
// Remove URL monitoring listeners
window.removeEventListener('popstate', this.handlePopState);
window.removeEventListener('hashchange', this.handleHashChange);
}
setupMonitoring() {
@ -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

1
npm/packs/aspnetcore.mvc.ui.embedding/src/abp-embedding.css

@ -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 */

64
npm/packs/aspnetcore.mvc.ui.embedding/src/abp-embedding.js

@ -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);
}
}
@ -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;
}
}
}
@ -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) ||
@ -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);
}
}

Loading…
Cancel
Save