Flask page not refreshing in the browser after xmlHttpRequest() send - javascript

I am using an event listener to intercept a form submission, wait for a confirmation and then submit the form using xmlHttpRequest. The POST to my page seems to be working (meaning I can detect all the form fields I would expect) however, at the end of the Flask view handler code, the redirect does not cause the browser to refresh the page. In the view handler I try to redirect the page using return redirect('/pagename')
The positive outcome in my event listener handler code is:
// The positive action was chosen
.then(() => {
url = event.submitter.formAction
submit_element = event.submitter;
len = event.srcElement.length
const formData = new FormData();
for (i = 0; i < len; i++) {
formData.append(event.srcElement[i].name, event.srcElement[i].value )
}
const request = new XMLHttpRequest();
request.open("POST", url, true);
request.send(formData);
})
In my flask app I also have:
#app.after_request
def after_request(response):
"""Ensure responses aren't cached"""
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
response.headers['Cache-Control'] = 'public, max-age=0'
return response
If I don't intercept the form submission with the event listener then the form submission and page redirect works as expected.
Is there something else I need to include in my xmlHttpRequest.send() call to fully simulate the form submission? Any ideas about what might be going on here much appreciated.

Using the JavaScript fetch() instead of the XMLHttpRequest() worked for me to solve my issue. The fetch() function POSTS the data and I can use the .then() clause to redirect to the location after the fetch().
I was able to use code like below to POST all the fields from the form submit, intercepted by my event listener; and then force a page reload of the page that the form was submitted from.
.
.
// get url of submitting page
url = event.submitter.formAction;
// work out how many fields we have
len = event.srcElement.length;
// add each field to a formData object
const formData = new FormData();
for (i = 0; i < len; i++) {
formData.append(event.srcElement[i].name, event.srcElement[i].value );
}
// post the data
response = fetch(url,
{
body: formData,
method: "post"
});
response.then(response => {
console.log(response);
// reload the page
document.location = response.url;
.
.

Related

How to show alerts on bad request, but redirect on success?

I have a registration form, the user is being redirected to home.php after success (works)
But also all the 'alerts/errors' which are echos in PHP, after submit, will redirect to register.php and show the error in blank white page.
(How do i display them to <div class="msg"> position?)
<script>
document.querySelector(".register form").addEventListener("submit", async (e) => {
e.preventDefault()
const form = e.target
const body = new FormData(form)
// fetch is much easier to use than XHR
const res = await fetch(form.action, {
method: "POST",
headers: {accept: "application/json", // let PHP know what type of response we want},
body})
const data = await res.json()
if (res.ok) {
location.href = data.location
} else if (res.status === 400) {
document.querySelector('.msg').textContent = data.message
// also do something with data.errors maybe
}
})
</script>
<body>
<div class="msg"></div> <!--position for error/ wrong pass etc-->
register.php
Based off that, please provide a correct code snippet in order to mark this as resolved.
It would probably make your life quite a bit easier if you always returned JSON from your PHP, rather than sometimes HTML as well.
For examples, when checking the errors at the top of register.php, you should return JSON objects --- e.g.
{error: "Email is not valid!"}
rather than their HTML equivilents.
This means that in your fetch, you'll now always be able to get the JSON content (currently, you'd probably get an error in your browser's debug console if one of those messages came back, as it's not valid JSON). Then, in your JavaScript, you can just detect this and switch however you want:
if (data.error) { // If there is an error
document.querySelector(".msg").textContent = data.error;
}
else if (data.location) { // If we want to redirect the user somewhere
window.location.href = "./" + data.location;
}

Send Cookie via JS to PHP

I am trying to send a String within Javascript to my Server. I made it create a cookie and right now I would like it to do stuff (for now: a simple echo). This is my button-function (I am using Bokeh):
const d = s.data;
var clustOut = "Nevermind the real input";
if (numberOfColors.length >= 2) {
localStorage.setItem("input", clustOut);
document.cookie = ('clustOut=' + clustOut + ';max-age=10368000;' + ';path=/');
window.location.href = "/php-scripts/post_cluster.php";
//alert(numberOfColors.length + "\\n" + clustOut);
//alert(d["x"] + d["y"] + d["color"]);
} else {
alert ("You have to assign the points to at least two clusters!");
}
My PHP-Files should simply echo:
<?php
$clustOut = $_COOKIE['clustOut'];
echo $clustOut;
?>
I am pretty sure that window.location.href = "/php-scripts/post_cluster.php"; might be the wrong command for a submit. What can I do to make my PHP-Script get the Cookie that I just set?
Sending data with the Fetch API.
The client and server can communicate with each other with the HTTP protocol. Whenever you load a webpage a HTTP request is send to the server and a response is send back to the client. You can make your own requests and talk to the server through the same protocol with the Fetch API.
You send the data to the server and wait for a response to come back. This way you can check what the server received and maybe do something with the response you got back.
let data = {
clustOut: "Nevermind the real input"
};
fetch('/php-scripts/post_cluster.php', {
method: 'POST',
body: JSON.stringify(data)
}).then(response => {
if (response.status === 200 && response.ok) {
return response.text();
}
}).then(message => {
console.log(message);
});
Sending data with XMLHttpRequest (IE10+)
For browsers that don't support the Fetch API fall back to the older XMLHttpRequest. It does the same thing, but is written differently.
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if (this.status === 200) {
console.log(this.responseText);
}
}
xhr.open('POST', '/php-scripts/post_cluster.php');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(JSON.stringify(data));
Sending data with the a form.
A more analogue approach would be to use a <form> element with the action attribute pointing towards your PHP script. This will also send a request to the PHP file while reloading the page. However, reading out the response works differently as you need to display the response on the page during rendering to see the outcome.
<form method="POST" action="/php-scripts/post_cluster.php">
<input type="hidden" name="clustOut" value="Nevermind the real input">
<button type="submit">Send</button>
</form>
Receiving data on server
Because in the examples above we've used the POST method to send our data, we'll need to access the global $_POST variable in PHP to read out the data that has been send. The value that is being returned or echoed will be send back in the response to the client.
<?php
$clust_out = isset( $_POST[ 'clustOut' ] ) ? $_POST[ 'clustOut' ] : '';
return $clust_out . ' has been received';
?>

How to access array in another page after sending through ajax

I have a array student. I need to pass this array in another php page via POST, not from GET, because it can contains thousands of characters.
I am trying to open new page sheet.php and echo the array student, I do simply checking echo $_POST['mnu'], but it is showing undefined index error.
var http = null;
if(window.XMLHttpRequest){
http = new XMLHttpRequest();
}
else{
http = new ActiveXObject('Microsoft.XMLHTTP');
}
http.open('POST','sheet.php',true);
http.setRequestHeader('Content-type','application/x-www-form-urlencoded');
http.onreadystatechange = function(){
if(http.readyState==4 && http.status==200){
window.open('sheet.php','_blank')
}
}
http.send('mnu='+JSON.stringify(student));
Like #RamRaider commented.. you're making two requests to sheet.php. The first being a "silent" POST request and the second being a GET request after the first POST request has successfully completed.
The second request won't share the payload of the first.
If I under stand correctly the below code should do what you are wanting...
// Create a form element
// <form action="sheet.php" method="post"></form>
var tempForm = document.createElement('form');
tempForm.setAttribute('action', 'sheet.php');
tempForm.setAttribute('method', 'POST');
tempForm.setAttribute('target', '_blank'); // Open in new tab
// Create an input field
// <input name="mnu" value="...">
var tempInput = document.createElement('input');
tempInput.setAttribute('name', 'mnu');
tempInput.setAttribute('value', JSON.stringify(student)); // Set field value
// Add the input to the form
tempForm.appendChild(tempInput);
// Add the form to the body in order to post
document.body.appendChild(tempForm);
// Submit the form
tempForm.submit();
// Remove the form
document.body.removeChild(tempForm);
And if you're using jQuery you can simplify the above code..
$('<form>', {
action: 'sheet.php',
method: 'POST',
target: '_blank',
html: $('<input>', {
name: 'mnu',
value: JSON.stringify(student)
}).prop('outerHTML')
}).appendTo($('body')).submit().remove();
Change http.send('mnu='+JSON.stringify(student)); to http.send(JSON.stringify(student));
And then in your sheet.php use json_decode($_POST) to fetch your POST data

Append antiforgery token to XMLHttpRequest

Pretty much what the title says. Here's the javascript... Works fine when not validating the token. Doesn't appear to see it when validating as I get The required anti-forgery form field "__RequestVerificationToken" is not present. error.
var downloadEmailSignature = function (control) {
if (control) {
let form = $(control).closest('form');
let token = $(form).find('input[name="__RequestVerificationToken"]').val();
if (form) {
let request = new XMLHttpRequest();
request.open("POST", "Forms/DownloadEmailSignature");
request.responseType = "blob";
request.setRequestHeader('RequestVerificationToken', token);
request.data = form.serialize();
request.onload = function () {
if (window.clientData.browser.name === "Internet Explorer") {
window.navigator.msSaveBlob(this.response, "EmailSignature.hta");
}
else{
let url = window.URL.createObjectURL(this.response);
let a = document.createElement("a");
document.body.appendChild(a);
a.href = url;
a.download = this.response.name || "download-" + $.now();
a.click();
}
};
console.dir(request);
request.send();
}
}
};
and the code behind...
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult DownloadEmailSignature(string emailSignature)
{
var hta = (MediaItem)SitecoreContext.GetItem<Item>(new Guid("{EE806F14-5BD3-491C-9169-DA701357FB45}"));
using (var reader = new StreamReader(MediaManager.GetMedia(hta).GetStream().Stream))
{
var htaText = reader.ReadToEnd();
htaText = htaText.Replace("*CARDSTRING*", emailSignature);
var stream = new MemoryStream(htaText.ToASCIIByteArray());
return new FileStreamResult(stream, "application/hta");
}
}
And finally the view...
<form id="download-email-signature" method="POST" enctype="multipart/form-data">
#Html.HiddenFor(m => m.EmailSignatureMarkup)
#Html.AntiForgeryToken()
#Html.FormIdentifier("FormsController", "DownloadEmailSignature")
Download installer
</form>
If you can replace XMLHttpRequest with $.ajax (as you already have JQuery loaded), the below segment should work.
let form = $(control).closest('form');
let token = $(form).find('input[name="__RequestVerificationToken"]').val();
$.ajax({
url: '/Home/DownloadEmailSignature',
type: 'POST',
data: {
__RequestVerificationToken: token,
emailSignature: 'emailSignature value'
},
success: function (result) {
alert(result);
//your code ....
}
});
According to the answer here:
ValidateAntiForgeryToken purpose, explanation and example
MVC's anti-forgery support writes a unique value to an HTTP-only
cookie and then the same value is written to the form. When the page
is submitted, an error is raised if the cookie value doesn't match the
form value.
Which implies the cookie MUST go back to the server. This should be working fine unless you are using IE9 (which is known to be problematic with this issue).
I recommend you include request.withCredentials = true; to eliminate any strange CORs related issue.
If a proxy is involved, the cookie may be getting stripped on the way back to the server too.

Using Javascript to add custom http header and trigger file download

I would like to start a simple file download through the browser, however an access token must be passed with a custom HTTP header:
GET https://my.site.com/some/file
Authorization: access_token
How can I inject the Authorization: header following the site URL?
I know it's possible to do that using query string, but I want to do it using headers.
I'm familiar with XMLHttpRequest, but as far as I understand it does not trigger download, it only reads content and the file I want to download is few hundred MBs at least.
xhr.setRequestHeader('Authorization', 'access_token');
This looks like a simple task, but I'm inexperienced coder so any help would be nice. Thanks.
I think this solves your problem:
function toBinaryString(data) {
var ret = [];
var len = data.length;
var byte;
for (var i = 0; i < len; i++) {
byte=( data.charCodeAt(i) & 0xFF )>>> 0;
ret.push( String.fromCharCode(byte) );
}
return ret.join('');
}
var xhr = new XMLHttpRequest;
xhr.open( "GET", "https://my.site.com/some/file" );
xhr.addEventListener( "load", function(){
var data = toBinaryString(this.responseText);
data = "data:application/text;base64,"+btoa(data);
document.location = data;
}, false);
xhr.setRequestHeader("Authorization", "access_token" );
xhr.overrideMimeType( "application/octet-stream; charset=x-user-defined;" );
xhr.send(null);
Modified answer https://stackoverflow.com/a/10518190/2767026 to fit your needs.
https://stackoverflow.com/a/12372670/1961561 could be a solution for your problem.
$.ajax({
url: "/test",
headers: {"X-Test-Header": "test-value"}
});
It is not possible to add custom http header when you download a file by clicking on a link.
However, in your use case, you might store the token in a cookie, which will be automatically added to all browser requests.

Categories

Resources