We can use window.location.replace
to avoid history, and to target on-page anchors without page reloads, but *not in iframes?
The problem is a CSP (content security policy) violation, which states script-src 'unsafe-inline'
must be enabled. Except I don't have a CSP defined, and even if I define one and allow script-src 'unsafe-inline'
it still gives the same violation error. Same result in ie11/chrome/ff.
iframe on the same domain (in the same directory).
- Target the iframe in the console and use
window.location.replace('/samepage.html#onpage_anchor')
in console. - It works. It targets the on page anchor without reloading the page, and without history.
- Put the same code inline on anchor links and it works.
- Use the same code in external script, get the csp violation error. This works fine if not in an iframe.
I tried creating a CSP to allow the action, but not even the most permissive content security policies possible would allow it.
So I put together examples on plunker which allows multiple files so I could use proper hrefs which reference the parent/child pages.
Notes about the plunker examples:
- The problem is not reproduced in these examples. The script works perfectly, even in the iframe. However, the same code does not work on my local server, or when I run it live on a VPS.
- I suspect the CSP violation doesn't get triggered on plunker because plunker is presenting content to the browser via a kind of abstraction layer of some sort.
- The first time you click the accordion links in the parent, it causes a refresh. This is because the way the page initially loads it doesn't reference index.html. Subsequent clicks work as expected without page reloads. Not an issue in the iframe because it does initially reference child.html
- These are good examples to show the code without requiring alterations to make it work (as in the need to change the hrefs to make them work in stackoverflow snippets, mentioned below). It is also good as it shows the javascript working as it should. But it does not show the actually problem. You will still need to load it up in your editor and run it on a local server or live hosting environment to see the real problem.
Plunker examples: With script/without history. Without script/with history
Simple accordion with one entry. Sufficient to reproduce issue.
Clicking open/close will expand/collapse accordion, no JS required. The JS should do the exact same thing but without history. Works fine, but not in an iframe.
Code snippet notes:
You can run the snippet to get an idea about what I am describing, but it does not actually demonstrate the issue.
The snippet does not behave the way it would in a real browser, the javascript does not work.
The snippet shows the code, but it should be run in an iframe to see the issue. Run it outside an iframe to see the difference and how it should work.
Because of how the links work with the JS (replacing the whole url) they actually must be like this
href="/thispage.html#ac1"
rather than justhref="#ac1"
as they appear in the snippet (can't target the actual html page in the snippet). So if you try this in your editor (please do), then remember to change the links to this formatthis_document.html#anchor
so they are still same page anchors, but the page.html is included in the link.
$(document).ready(function() {
// anchor links without history
$.acAnch = function(event) {
event.preventDefault();
var anchLnk = $(event.target);
var anchTrgt = anchLnk.attr('href');
window.location.replace(anchTrgt);
}
// listen for anchor clicks
$('.accordion').on('click', 'a', $.acAnch);
});
div#sample.example .accordion {
margin-left: 50px;
margin-top: 50px;
}
div#sample.example section {
box-sizing: border-box;
clear: both;
position: relative;
display: block;
width: 300px;
height: 32px;
padding: 0;
background-color: #fff;
box-shadow: inset 0 0 1px 1px #000;
overflow: hidden;
}
div#sample.example section:target {
height: auto;
}
div#sample.example a {
box-sizing: border-box;
display: block;
float: right;
width: 50%;
height: 32px;
margin: 0;
padding: 4px;
text-align: center;
font-size: 16px;
color: #000;
background-color: #fff;
box-shadow: inset 0 0 1px 1px #000;
}
div#sample.example p {
box-sizing: border-box;
clear: both;
display: block;
width: 100%;
padding: 16px;
margin: 16px 0 0;
text-align: center;
color: #000;
}
<script src=".3.1/jquery.min.js"></script>
<div id="sample" class="example">
<article class="accordion">
<section id="ac1">
<a href="#ac0" class="ac-close">Close</a>
<a href="#ac1" class="ac-open">Open</a>
<div class="ac-content">
<p>The elephants talking in their sleep kept me up so late.</p>
</div>
</section>
</article>
</div>
We can use window.location.replace
to avoid history, and to target on-page anchors without page reloads, but *not in iframes?
The problem is a CSP (content security policy) violation, which states script-src 'unsafe-inline'
must be enabled. Except I don't have a CSP defined, and even if I define one and allow script-src 'unsafe-inline'
it still gives the same violation error. Same result in ie11/chrome/ff.
iframe on the same domain (in the same directory).
- Target the iframe in the console and use
window.location.replace('/samepage.html#onpage_anchor')
in console. - It works. It targets the on page anchor without reloading the page, and without history.
- Put the same code inline on anchor links and it works.
- Use the same code in external script, get the csp violation error. This works fine if not in an iframe.
I tried creating a CSP to allow the action, but not even the most permissive content security policies possible would allow it.
So I put together examples on plunker which allows multiple files so I could use proper hrefs which reference the parent/child pages.
Notes about the plunker examples:
- The problem is not reproduced in these examples. The script works perfectly, even in the iframe. However, the same code does not work on my local server, or when I run it live on a VPS.
- I suspect the CSP violation doesn't get triggered on plunker because plunker is presenting content to the browser via a kind of abstraction layer of some sort.
- The first time you click the accordion links in the parent, it causes a refresh. This is because the way the page initially loads it doesn't reference index.html. Subsequent clicks work as expected without page reloads. Not an issue in the iframe because it does initially reference child.html
- These are good examples to show the code without requiring alterations to make it work (as in the need to change the hrefs to make them work in stackoverflow snippets, mentioned below). It is also good as it shows the javascript working as it should. But it does not show the actually problem. You will still need to load it up in your editor and run it on a local server or live hosting environment to see the real problem.
Plunker examples: With script/without history. Without script/with history
Simple accordion with one entry. Sufficient to reproduce issue.
Clicking open/close will expand/collapse accordion, no JS required. The JS should do the exact same thing but without history. Works fine, but not in an iframe.
Code snippet notes:
You can run the snippet to get an idea about what I am describing, but it does not actually demonstrate the issue.
The snippet does not behave the way it would in a real browser, the javascript does not work.
The snippet shows the code, but it should be run in an iframe to see the issue. Run it outside an iframe to see the difference and how it should work.
Because of how the links work with the JS (replacing the whole url) they actually must be like this
href="/thispage.html#ac1"
rather than justhref="#ac1"
as they appear in the snippet (can't target the actual html page in the snippet). So if you try this in your editor (please do), then remember to change the links to this formatthis_document.html#anchor
so they are still same page anchors, but the page.html is included in the link.
$(document).ready(function() {
// anchor links without history
$.acAnch = function(event) {
event.preventDefault();
var anchLnk = $(event.target);
var anchTrgt = anchLnk.attr('href');
window.location.replace(anchTrgt);
}
// listen for anchor clicks
$('.accordion').on('click', 'a', $.acAnch);
});
div#sample.example .accordion {
margin-left: 50px;
margin-top: 50px;
}
div#sample.example section {
box-sizing: border-box;
clear: both;
position: relative;
display: block;
width: 300px;
height: 32px;
padding: 0;
background-color: #fff;
box-shadow: inset 0 0 1px 1px #000;
overflow: hidden;
}
div#sample.example section:target {
height: auto;
}
div#sample.example a {
box-sizing: border-box;
display: block;
float: right;
width: 50%;
height: 32px;
margin: 0;
padding: 4px;
text-align: center;
font-size: 16px;
color: #000;
background-color: #fff;
box-shadow: inset 0 0 1px 1px #000;
}
div#sample.example p {
box-sizing: border-box;
clear: both;
display: block;
width: 100%;
padding: 16px;
margin: 16px 0 0;
text-align: center;
color: #000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="sample" class="example">
<article class="accordion">
<section id="ac1">
<a href="#ac0" class="ac-close">Close</a>
<a href="#ac1" class="ac-open">Open</a>
<div class="ac-content">
<p>The elephants talking in their sleep kept me up so late.</p>
</div>
</section>
</article>
</div>
$(document).ready(function() {
// anchor links without history
$.acAnch = function(event) {
event.preventDefault();
var anchLnk = $(event.target);
var anchTrgt = anchLnk.attr('href');
window.location.replace(anchTrgt);
}
// listen for anchor clicks
$('.accordion').on('click', 'a', $.acAnch);
});
This is very simple:
- acAnch function takes the
href
attribute and drops it intowindow.location.replace()
. - Listen for clicks on anchors within the accordion to run the acAnch function.
So all the script does is run window.location.replace('/this_same_page.html#on_page_anchor')
If you put that in the console it works, no CSP violation. But running it from external script doesn't work.
Inline on the links works fine:
onclick="event.preventDefault();window.location.replace('/thispage.html#acc0');"
onclick="event.preventDefault();window.location.replace('/thispage.html#acc1');"
Putting that on the respective links works perfectly, but I really prefer not to use inline script like that. There must be a way to do this with an external script.
I tried running the javascript on parent instead of in the iframe (with modifications to select the links within the child of course). Same CSP error result.
Why am I doing this? Well the site is much more complex than the example. Anchors in iframes work fine but they add history. If you run the code above without the javascript, (or just run the snippet), open and close the accordion a few times, and use back button, it will go back through the open close states.
I wouldn't mind the history, but if it is in an iframe, when you leave the parent page and then come back to it, the history in the iframe is broken. Going back doesn't go back through the accordion states anymore, but instead just keeps reloading the iframe. Initially the anchors don't cause iframe reloads but just steps through accordion state history, which works fine, until you leave the page and come back. Then back no longer goes through the accordion states, but just goes through a pile of identical iframe reloads. It is very user unfriendly behavior.
I don't need to use location.replace if there is another method that will work. I have tried many other approaches though, and I've found that methods that can achieve the same result, generally result in the same error.
The goal is simply to activate the anchor links on page without reloading, and without history, inside an iframe.
The inline script works. Can we make it work in an external .js file?
Share Improve this question edited Aug 3, 2020 at 18:43 Braiam 4,49611 gold badges49 silver badges83 bronze badges asked Nov 14, 2019 at 4:28 Veneseme TyrasVeneseme Tyras 6064 silver badges9 bronze badges 6 | Show 1 more comment6 Answers
Reset to default 1This may be a non-issue, but you mentioned this is an issue on you local server, and I noticed your code relys on relative links.
If you are not setup correctly, you may be serving resource via the file:// protocol or somehow using a localhost, not recognized as a valid TLD, which would result in file:// protocol as default, or invalidate CSP
In any event, try using absolute URLs and see if that resolves the issue
yes you can use iframe with -window-location-replace for reference, you can use this ref link Javascript location.replace and iframe
You can toggle an active class to the parent element like this using anchor click events.
// Code goes here
$(window).on('load', function() {
$('.accordion section').on('click', '.ac-open', function(e) {
e.preventDefault();
$(e.target).parent().addClass('active');
});
$('.accordion section').on('click', '.ac-close', function(e) {
e.preventDefault();
$(e.target).parent().removeClass('active');
});
});
/* Styles go here */
html,
body {
margin: 0;
padding: 0;
}
.flt-lft {
float: left;
margin: 16px;
}
h4 {
text-align: center;
margin: 0;
padding: 8px;
color: white;
background-color: green;
}
/* #sample.example .accordion {
margin-left: 50px;
margin-top: 50px;
} */
#sample.example section {
box-sizing: border-box;
clear: both;
position: relative;
display: block;
width: 300px;
height: 32px;
padding: 0;
background-color: #fff;
box-shadow: inset 0 0 1px 1px #000;
overflow: hidden;
}
#sample.example section.active {
height: auto;
}
#sample.example a {
box-sizing: border-box;
display: block;
float: right;
width: 50%;
height: 32px;
margin: 0;
padding: 4px;
text-align: center;
font-size: 16px;
color: #000;
background-color: #fff;
box-shadow: inset 0 0 1px 1px #000;
}
#sample.example p {
box-sizing: border-box;
clear: both;
display: block;
width: 100%;
padding: 16px;
margin: 16px 0 0;
text-align: center;
color: #000;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<div class="flt-lft">
<h4>parent</h4>
<div id="sample" class="example">
<article class="accordion">
<section id="ac1">
<a href="index.html#ac0" class="ac-close">Close</a>
<a href="index.html#ac1" class="ac-open">Open</a>
<div class="ac-content">
<p>The elephants talking in their sleep kept me up so late.</p>
</div>
</section>
</article>
</div>
</div>
<div class="flt-lft">
<h4>iframe</h4>
<iframe src="child.html"></iframe>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="script.js"></script>
</body>
</html>
yes you can , here is a live example from below link
https://codepen.io/pmk/pen/wOwoyW
HTML
<div class="">
<h3>Testing 4 methods of writing dynamic content to Iframe.</h3>
<p>#1 use <strong>document.write()</strong>,
#2 use <strong>URL.createObjectURL()</strong>,
#3 use <strong>encodeURI()</strong> and #4 <strong>iframe.srcdoc</strong></p>
<p>Using the recommended method <strong>URL.createObjectURL()</strong> leads to problems when trying to retrieve the <strong>windown.location</strong> object. (Same does the <strong>encodeURI()</strong> method)<p/>
<p>Only reliable method if you need window.location, seems to be the old obsolete <strong>document.write()</strong> method.</p>
<iframe id="iframe1"></iframe>
<iframe id="iframe2"></iframe>
<iframe id="iframe3"></iframe>
<iframe id="iframe4"></iframe>
</div>
CSS
@import url(https://fonts.googleapis.com /css?family=Fira+Sans:400,500italic);
html {
height: 100%;
background-color: rgba(34,32,36,1);
}
body {
text-align: center;
font: normal 100% 'Fira Sans', sans-serif;
color: #aaa;
}
iframe {
width: 40%;
height: 200px;
background: white;
}
JS
var template = [
'<!DOCTYPE HTML>',
'<html>',
'<head>',
'</head>',
'<body>',
'<script>',
'document.write("<pre>" + JSON.stringify(window.location, null, 4) +
"</pre>");',
'<\/script>',
'</body>',
'</html>'
].join('');
var iframe1El = document.querySelector('#iframe1');
var iframe1 = iframe1El.contentWindow || (
iframe1El.contentDocument.document || iframe1El.contentDocument);
var iframe2El = document.querySelector('#iframe2');
var iframe2 = iframe2El.contentWindow ||
( iframe2El.contentDocument.document || iframe2El.contentDocument);
var iframe3El = document.querySelector('#iframe3');
var iframe3 = iframe3El.contentWindow ||
( iframe3El.contentDocument.document ||
iframe3El.contentDocument);
var iframe4El = document.querySelector('#iframe4');
var iframe4 = iframe4El.contentWindow ||
( iframe4El.contentDocument.document || iframe4El.contentDocument);
iframe1.document.open();
iframe1.document.write(template);
iframe1.document.close();
var bData = new Blob([template], {type: 'text/html'});
iframe2El.onload = function() { window.URL.revokeObjectURL(bData); };
iframe2El.src = window.URL.createObjectURL(bData);
iframe3El.src = 'data:text/html;charset=utf-8,' +
encodeURI(template);
iframe4El.srcdoc = template;
Yes you can use it with iframe.
you can use CSS instead of html iframe tag because iframe tag is removed in html
<a href="#ac0" class="ac-close">Close</a>
should work. – Rafael Herscovici Commented Nov 16, 2019 at 0:12