Form onsubmit in dynamic iframe do not get triggered - javascript

Update
the problem is not the iframe, the problem is the form not submitting by the onsubmit function that posts json. The goal is to dynamically create an iframe that redirects using a form post to another URL with the json content of the script tag above.
Original
I have the following on a sample website:
<script data-dashboard="true" type="application/json">
{
"title":"Serverless Identity"
}
</script>
<script type="text/javascript">
let dashboardConfiguration = document.querySelector("script[data-dashboard=\"true\"]");
if (dashboardConfiguration) {
let iframe = document.createElement("iframe");
let model = JSON.stringify(JSON.parse(dashboardConfiguration.innerHTML.trim()));
document.body.appendChild(iframe);
var doc = iframe.contentWindow.document;
doc.open()
doc.writeln(`<form id="form" action="https://localhost:44338/dashboard/" method="POST" target="_self"></form>`)
doc.close();
iframe.onload = () => {
let form = doc.getElementById("form");
form.addEventListener("submit", (e) => {
console.log(model);
e.preventDefault();
// construct an HTTP request
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action, true);
xhr.setRequestHeader('content-type', 'application/json; charset=UTF-8');
// send the collected data as JSON
xhr.send(model);
xhr.onloadend = function () {
// done
};
});
form.submit();
}
};
</script>
I also tried with onsubmit with the same result that it does a normal submit.

Its not possible,
when using form.submit() any onsubmit handlers will not trigger, as per spec.
I used a different approach og sending a normal form submit using form encoded values with the hole payload in one hidden field and deserialized it on serverside.

When you have created iframe then that it is loaded and then you are binding onload instead of that you should use DOMNodeInserted event on body when iframe is appended to the body like
let iframe = document.createElement("iframe");
let model = JSON.stringify(JSON.parse(dashboardConfiguration.innerHTML.trim()));
document.body.addEventListener("DOMNodeInserted", function (ev) {
//write your logic here
});
document.body.appendChild(iframe);
var doc = iframe.contentWindow.document;

Remove iframe.onload = () => { }. Check the chrome network tab. The form url https://localhost:44338/dashboard/ gets triggered.
<script data-dashboard="true" type="application/json">
{
"title":"Serverless Identity"
}
</script>
<script type="text/javascript">
let dashboardConfiguration = document.querySelector("script[data-dashboard=\"true\"]");
if (dashboardConfiguration) {
let iframe = document.createElement("iframe");
let model = JSON.stringify(JSON.parse(dashboardConfiguration.innerHTML.trim()));
document.body.appendChild(iframe);
var doc = iframe.contentWindow.document;
doc.open()
doc.writeln(`<form id="form" action="https://localhost:44338/dashboard/" method="POST" target="_self"></form>`)
doc.close();
let form = doc.getElementById("form");
form.addEventListener("submit", (e) => {
console.log(model);
e.preventDefault();
// construct an HTTP request
var xhr = new XMLHttpRequest();
xhr.open(form.method, form.action, true);
xhr.setRequestHeader('content-type', 'application/json; charset=UTF-8');
// send the collected data as JSON
xhr.send(model);
xhr.onloadend = function () {
// done
};
});
form.submit();
}

Related

XMLHttpRequest POST redirects before I get the response

Javascript:
function basiclogin() {
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "login");
xhttp.send(new FormData(document.forms.login));
xhttp.onload = () => alert(xhttp.response);
}
HTML:
<html>
...
<script>basiclogin()</script>
</html>
This works and gives an alert from the server.
But if I call basiclogin() using a submit buttom in the form the page redirects and shows the response as a new document before I recieve the response!
Why is this?
The default behaviour of a submit button is to open the page to send the data to the server. If you wish to prevent this, you need to use event.preventDefault().
E.g.
function basiclogin(event) {
event.preventDefault();
var xhttp = new XMLHttpRequest();
xhttp.open("POST", "login");
xhttp.send(new FormData(document.forms.login));
xhttp.onload = () => alert(xhttp.response);
}
Then add the parameter to the submit button onClick handler.
E.g. something like:
submitButton.addEventListener('click', (event) => basicLogin(event))

Fetch API - Targeting an Iframe

Using the target attribute of an html form, you can post form data and then have the html-server-response showcased in an iframe.
form.setAttribute("target", "nameOfIframe");
Can the same thing be achieved posting with the ecmascript fetch api (without creating an actual html form element)?
It's fairly trivial to update the content of an IFRAME with JavaScript/EcmaScript and can be done using the srcdoc property.
document.getElementById('fetchTarget').srcdoc = `<!DOCTYPE html><p>Hello World!</p>`;
<iframe id="fetchTarget">
</iframe>
All you would need to do is arrange to update it as part of the processing of the call to fetch:
fetch(`https://jsonplaceholder.typicode.com/posts/1`)
.then(response => {
console.log(response);
if (response.ok){
return response.text();
}
})
.then(text => {
document.getElementById('fetchTarget').srcdoc = text;
});
<iframe id="fetchTarget">
</iframe>
This URL isn't a particularly great example as it returns JSON instead of HTML but it doesn't really affect the result.
I was able to get it the fetch POST response into the iframe by using the srcdoc property of the iframe, as advised to me by this AI answer.
async function sendFetchResponseToIframe()
{
const formData = new FormData();
formData.append('input1', 'value1');
formData.append('input2', 'value2');
let options = {};
options.method = 'POST';
options.mode = 'cors';
options.body = formData;
let postURL = 'http://neartalk.com/test/formData.php';
let response = await fetch(postURL, options);
let data = await response.text();
let iframe = document.getElementById("frame1");
iframe.srcdoc = data;
}
sendFetchResponseToIframe();
<p>The fieldset below contains an iframe that gets updated to the Fetch POST response:</p>
<fieldset>
<legend>Iframe</legend>
<iframe id="frame1" style="width:100%" src="http://www.example.com/" frameborder="0"></iframe>
</fieldset>
However, this method is not as transparent as setting the target of an HTML form, which preserves all relative links that may be returned by a server's HTML response.
While there are methods of addressing this, like: (1) Modifying the html response to include a base element, or (2) modifying the HTML response to convert all relative links to absolute links. I can still imagine other complications like server-side redirects that could ultimately occur. Such redirects would lead to you ultimately having to also modifying your fetch code to follow the redirects, determine the final URL, and convert the base URL or absolute links accordingly.
Due to these complications, the answer to my question is ultimately: No; you're better off creating a hidden HTML form dynamically, and setting its target prior to submission, and then ultimately removing that dynamically created form after submission. This method more easily protects the relative links that could potentially be in the HTML response:
function submitFormToIframe(actionURL, formData, iframe)
{
const form = document.createElement('form');
form.action = actionURL;
form.method = 'POST';
form.target = iframe.name;
for (const [name, value] of formData.entries())
{
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
form.appendChild(input);
}
form.style.display = "none";
document.body.append(form);
form.submit();
document.body.removeChild(form);
}
async function main()
{
// Create Form Data:
const formData = new FormData();
formData.append('input1', 'value1');
formData.append('input2', 'value2');
// Get iframe from DOM:
let iframe = document.getElementById("frame1");
let actionURL = 'http://neartalk.com/test/formData.php';
submitFormToIframe(actionURL, formData, iframe);
}
onload = main;
<p>The fieldset below contains an iframe that gets updated via generated form submission:</p>
<fieldset>
<legend>Iframe</legend>
<iframe id="frame1" name="frame1" style="width:100%" src="http://www.example.com/" frameborder="0"></iframe>
</fieldset>
Due to these findings, I think the specification writers, should consider adding a fetch option that allows you to send a fetch response directly to a specified iframe, in a manner where relative links aren't susceptible to breakage (as is the case with HTMLFormElement.target. This would make the fetch API a more attractive solution.
If you need to process the data inside the iframe, you can use window.postMessage(). Instead of iframe.srcdoc property, you can of course put the code in a separate file and reference it via iframe.src
let btn = document.getElementById('btn')
let otherBtn = document.getElementById('btn2')
let wrongBtn = document.getElementById('btn3')
let results = document.getElementById('results')
function showResults(json) {
results.contentWindow.postMessage({
action: 'showResults',
data: json,
}, "*");
}
function sendForm() {
fetch('https://dummyjson.com/products/1')
.then(res => res.json())
.then(showResults)
}
function addLinkToIframe() {
results.contentWindow.postMessage({
action: 'addLink',
data: {
href: 'index.html',
text: 'Click me!',
},
}, "*");
}
function addCallNonExistentAction() {
results.contentWindow.postMessage({
action: 'gibberish',
data: 'even more gibberish',
}, "*");
}
btn.addEventListener('click', sendForm)
otherBtn.addEventListener('click', addLinkToIframe)
wrongBtn.addEventListener('click', addCallNonExistentAction)
window.addEventListener('message', (event) => {
if (event.data.action === 'iframe::DOMContentLoaded') {
console.log('iframe loaded');
results.contentWindow.postMessage({
action: 'setBase',
data: document.baseURI,
}, "*");
} else {
alert(`Action '${event.data.action}' not defined`)
}
})
<button id="btn">Send</button>
<button id="btn2">Add link</button>
<button id="btn3">Call non-existent action</button>
<hr />
<iframe id="results" srcdoc="<!DOCTYPE html>
<html>
<head>
<base href=>
<script>
window.addEventListener('message', (event) => {
if (event.data.action === 'showResults') {
document.body.querySelector('pre').appendChild(
document.createTextNode(JSON.stringify(event.data.data, true, '\t'))
);
} else if (event.data.action === 'setBase') {
console.log('setbase')
document.head.querySelector('base').href = event.data.data
console.log(document.baseURI)
} else if (event.data.action === 'addLink') {
link = Object.assign(document.createElement('a'), {
href: event.data.data.href,
})
link.textContent = event.data.data.text
console.log('add', link)
document.body.prepend(link);
} else {
alert(`Action '${event.data.action}' not defined`)
}
})
window.parent.postMessage({
action: 'iframe::DOMContentLoaded',
}, '*')
</script>
</head>
<body>
Waiting ...
<pre></pre>
</body>
</html>
"></iframe>

Submit a form obtained via XMLHttpRequest?

I am trying to a download a html page via javascript, parse it and submit the form with the following code. Everything seems to work perfectly in this function, yet I am unable to see the desired server side changes. Could someone point me if there's something wrong in this approach ?
function get_page(url){
var xhr = new XMLHttpRequest();
xhr.responseType = "document"; //parse html
xhr.open("GET", url);
xhr.send(null);
xhr.onload = function(){
// get form here
var dom = xhr.responseXML;
var form = dom.forms[0];
// set values in fields
form[0].value='hello';
form[1].value=form[0].value;
//change action from # to url
form.action = url;
//EDIT: attach form to body
document.getElementsByTagName('body')[0].appendChild(form);
//form submit
form.submit();
//print form last value
console.log(form[3].value);
}
}

Javascript - Posting a form with AJAX and getting return values

I have now got everything to post correctly but this script keeps loading it into a new page. Is it something to do with the way my php file returns it? "echo(json_encode($return_receipt));"
<script>
// Get XML HTTP Type
function get_XmlHttp() {
var xmlHttp = null;
if(window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();
}else if(window.ActiveXObject) {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
return xmlHttp;
}
function ajaxSuccess () {
alert(this.responseText);
}
function ajaxrequest(oFormElement,tagID) {
//Get The Correct XMLHTTP Object
var request = new XMLHttpRequest();
request.onload = ajaxSuccess;
request.open(oFormElement.method, oFormElement.action, true);
//request.setRequestHeader("Content-type","application/x-www-form-urlencoded");
request.send(new FormData(oFormElement));
}
</script>
you have two easy options..
1.you can use jquery ajax method... or
2. javascript form submission without loading the page by targetting the form to the hidden inline frame
here i'm using it for image upload image name without hitting submit button and save the data to the database table without reloading the page.. edit it according to your convenience.
function upload(img)//Javascript function
{
var types = /\.jpg|\.gif|\.jpeg/i;
var filename = img.value;
if (filename.search(types) == -1) {
alert("Select only images ( jpg or gif or jpeg format)");
img.form.reset();
return false;
}
img.form.action = 'upload-picture.php';
img.form.target = 'iframe';
img.form.submit();
return true;
}
// here is iframe on the same page
<iframe name="iframe" id="u_iframe" style="display:none;"></iframe>
<input type="file" name="picture" id="picture" onchange="return upload(this);" />

POST Request (Javascript)

How do you make a simple POST request in Javascript without using a forms and without posting back?
Though I am taking the code sample from #sundeep answer, but posting the code here for completeness
var url = "sample-url.php";
var params = "lorem=ipsum&name=alpha";
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
//Send the proper header information along with the request
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send(params);
You can do this using AJAX calls (XMLHttpRequest object)
http://www.openjs.com/articles/ajax_xmlhttp_using_post.php
I have made a function that send a request without refresh the page, without open a page and without AJAX. The proccess is invisible to the user. I use a false iframe to send a request:
/**
* Make a request without ajax and without refresh the page
* Invisible for the user
* #param url string
* #param params object
* #param method string get or post
**/
function requestWithoutAjax( url, params, method ){
params = params || {};
method = method || "post";
// function to remove the iframe
var removeIframe = function( iframe ){
iframe.parentElement.removeChild(iframe);
};
// make a iframe...
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.onload = function(){
var iframeDoc = this.contentWindow.document;
// Make a invisible form
var form = iframeDoc.createElement('form');
form.method = method;
form.action = url;
iframeDoc.body.appendChild(form);
// pass the parameters
for( var name in params ){
var input = iframeDoc.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = params[name];
form.appendChild(input);
}
form.submit();
// remove the iframe
setTimeout( function(){
removeIframe(iframe);
}, 500);
};
document.body.appendChild(iframe);
}
Now you can do it:
requestWithoutAjax('url/to', { id: 2, price: 2.5, lastname: 'Gamez'});
See how works!: http://jsfiddle.net/b87pzbye/10/.

Categories