Submitting a form in a document retrieved using a DOMParser - javascript

I'm writing automation software in JavaScript within TamperMonkey.
My script does few AJAX requests which return HTML that I parse using a DOMParser. I was wondering if it was possible to submit these forms without having to actually open the newly returned HTML in the main page.

Yes. You need to get your form from the document body, create a FormData object and set field values, and send a POST request.
For simplicity sake I used the Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API. I also assumed that the credentials also needed to be passed from the same origin.
fetch('<GET URL>', {method: "GET", credentials: 'same-origin'})
.then((response) => (response.text()))
.then((responseBody) => {
var html = htmlBody(responseBody);
var form = html.querySelector('#my-form'); // whatever your form is called
var formData = new FormData();
formData.append('someName', 'someValue'); // the field name will probably come from your form fields
postForm(formData)
.then((response) => (response.text()))
.then((responseBody) => {
// whatever with the form post response
});
})
function htmlBody(string) {
var document = new DOMParser;
return document.parseFromString(string, 'text/html').body;
}
function postForm(formData) {
return fetch('<POST URL>', {
method: 'POST',
body: formData,
credentials: 'same-origin'
})
}

Related

jQuery $.AJAX to replace with JavaScript fetch - mvc4 method arguments are always null

We had a working jQuery script which calls an MVC4 C# controller method like this:
// C# controller method
public ActionResult MyMethod(String text, String number)
{
... do something ...
... returns a partial view html result ...
}
// javascript call
var myData = { text:"something", number: "12" };
$.ajax({ url:ourUrl, type:"GET", data: myData,
success: function(data) { processAjaxHtml( data )}});
Now we want to replace this $.ajax call to a native fetch(...) call like this (and use a Promise):
function startAjaxHtmlCall(url) {
var result = fetch( url, {method: "GET", body: myData});
return result.then( function(resp) { return resp.text(); });
}
starAjaxCall(ourUrl).then( resp => processAjaxHtml(resp) );
We have problems, and somehow we cannot find the answers:
using GET we cannot attach body parameters (I see that the html GET and POST calls are quite different, but the $.ajax somehow resolved this problem)
we changed the GET to POST but the controller method still got "null"-s in the parameters
we changed the fetch call to "body: JSON.stringify(myData)" but the method still gots null
we constructed a temp class with the 2 properties, changed the method parameters as well - but the properties still contains null
we added to the [FromBody] attribute before the method class parameter but it got still nulls
we replace body: JSON.stringify(myData) to body: myData - still the same
Note: we tried this in Firefox and Chrome, the code is MVC5, C#, .NET Framework 4.5 (not .CORE!) hosted by IIS.
We changed the javascript call as the following (and everything works again):
var promise = new Promise((resolve, reject) => {
$.ajax({
url: url,
method: method,
data: myData,
success: function(data) { resolve(data); },
error: function(error) { reject(error); },
});
});
return promise;
// note: .then( function(resp) { return resp.text(); }) // needs no more
So: what do we wrong? what is the information we do not know, do not understand about fetch? How to replace $.ajax to fetch in this situation correctly? can it works with GET again? how to modify the controller method to receive the arguments?
GET requests do not have a BODY, they have querystring parameters. Using URLSearchParams makes it easy
var myData = { text:"something", number: "12" };
return fetch('https://example.com?' + new URLSearchParams(myData))
.then( function(resp) { return resp.text(); })
Other way of building the URL
const url = new URL('https://example.com');
url.search = new URLSearchParams(myData).toString();
return fetch(url)...
If you were planning on sending JSON to the server with a post request
fetch('https://example.com', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(myData)
});

AttributeError: 'NoneType' object has no attribute 'decode' when sending a form through JS

I'm trying to send some data through a form using JavaScript Fetch to a Django view, including an image. I keep getting this error message nine times as if no data was sent to the back end:
My view is as follows:
if "contributors" in data:
try:
Project.objects.get(title=data['title'])
return JsonResponse({'error': 'Project title already exists!'}, status=406)
except Project.DoesNotExist:
form = ProjectForm(request.POST, request.FILES)
project = Project.objects.create(
title=data['title'],
description=data['description'], logo=data['logo'])
return JsonResponse({"message": "Project successfully created!"}, status=201)
and my JavaScript:
const projectName = document.getElementById("project_name");
const contributors = document.getElementById("id_contributors");
const description = document.getElementById("id_description");
const logo = document.getElementById("id_logo");
const projectCsrf = document.getElementsByName("csrfmiddlewaretoken")[0];
document.getElementById("submitProjectForm").addEventListener("click", () => {
let formData = {
title: projectName.value,
contributors: contributors.value,
description: description.value,
logo: logo.files[0],
};
submitForm(projectCsrf, formData);
});
function submitForm(csrf, fields) {
const request = new Request(window.location.origin, {
headers: {
"X-CSRFToken": csrf.value,
"Content-Type": "multipart/form-data",
},
});
fetch(request, {
method: "POST",
body: JSON.stringify(fields),
})
.then((response) => response.json())
.then((result) => alert(result.message ? result.message : result.error))
.catch((err) => console.log(err));
}
is it maybe due to python's Json.loads method not being able to decode the JavaScript File object? Thanks in advance!
As you are including an image in your data and your data is a formdata why you are converting it to a string:
fetch(request, {
method: "POST",
body: JSON.stringify(fields),
})
I think you should add formdata itself to body of fetch api
fetch(request, {
method: "POST",
body: fields,
})
So after hours of debugging I managed to figure it out. Ali javanmardi was partially right in that i should not have been converting the data to JSON because I was sending files.
The main issue for this error was in my headers in my fetch function:
"Content-Type": "multipart/form-data",
This appeared to be causing the main issue, when I changed this to:
"X-Requested-With": "XMLHttpRequest"
it worked.
I also converted all of the data collected from the form into FormData, rather than creating my own object:
let formData = new FormData();
formData.append("logo", logo.files[0]);
formData.append("title", projectName.value);
formData.append("contributors", contributors.value);
formData.append("description", description.value);
On the back end in my view, I should have been saving the form instance instead of creating a new Project object:
new_form = ProjectForm(request.POST, request.FILES)
if new_form.is_valid():
new_form.save()
Now I can upload images fine.

How to process dynamic added html form on NodeJS express

My JQuery code appends html form to the DOM like this.
$("#form-container").empty().append("<form method='POST' action='/process-form' id='my-form'>
<input name='myfield'>
<button type='submit'>Submit</button>
</form>")
And my server side code looks like this
//I have app.use(bodyParser.urlencoded({extended: true}) middleware
router.post('/process-form',function(req,res){
console.log(req.body)
})
The console.log returns an empty object. However, when I create a new page with just the form and submit the form, it returns the form values as expected. Any help why it behaves like that?
EDIT:
JQuery code for AJAX request
$(document).on('submit', '#my-form', function(evt) {
evt.preventDefault();
let url = $(this).attr('action')
let method = $(this).attr('post')
$.ajax({
url,
type: 'POST',
success: function(data) {
console.log(data)
},
error: function(err) {
console.log(err)
}
})
})
You aren't sending any data with our Ajax post. You have to get the data from the form and send it yourself for an Ajax call.
There are numerous ways to get the data out of the form and package it up for jQuery. One way which puts it in the URLencoded form is to get the DOM element for the form and execute const formData = form.serialize() and then send that in the data property for $.ajax().
You could put that together like this:
$(document).on('submit', '#my-form', function(evt) {
evt.preventDefault();
let url = $(this).attr('action')
let method = $(this).attr('post')
$.ajax({
url,
type: 'POST',
data: $(this).serialize(),
success: function(data) {
console.log(data)
},
error: function(err) {
console.log(err)
}
})
});

How can I post a json object and a file to asp.net server with fetch

I'm a bit stuck here, I'm trying to post a json object from my indexedDb simultaneously with a IFormFile object to the server. The method that accepts it looks like this:
[HttpPost]
public async Task<IActionResult> Create(BatchModel model, IFormFile vinFile)
{
//logic goes here
}
This method worked earlier, before I had to store my batchModels as Json objects, and were just posted straight from the form with a POST. A lot has changed since then, and now the client will upload it as soon as he gets online again from an offline state, with the following (simplified) method:
if (infoChanged === true) {
fetchPromises.push(
fetch('/Batch/Create/', {
headers: new Headers({
'Content-Type': 'application/javascript' //Tried this with multi-part/form
}),
credentials: 'same-origin',
method: 'POST',
body: batch //, vinFile
})
);
}
return Promise.all(fetchPromises);
For testing I tried to use the method with only the model filled, and the only thing I had to change was that I had to change in the C# code was adding a [FromBody] tag, but now I need the vinFile filled as well.
I tried it already with a FormData object, appending the batch and the vinFile with the same name as in the Create function. But that would result in both variables as null.
The key feature you need to look at is your header. You can change your response to JSON if you do header: { Content-Type: "application/json" }
You want something like this: You need to configure the type yourself.
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); });
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch

File Upload without Form

Without using any forms whatsoever, can I just send a file/files from <input type="file"> to 'upload.php' using POST method using jQuery. The input tag is not inside any form tag. It stands individually. So I don't want to use jQuery plugins like 'ajaxForm' or 'ajaxSubmit'.
You can use FormData to submit your data by a POST request. Here is a simple example:
var myFormData = new FormData();
myFormData.append('pictureFile', pictureInput.files[0]);
$.ajax({
url: 'upload.php',
type: 'POST',
processData: false, // important
contentType: false, // important
dataType : 'json',
data: myFormData
});
You don't have to use a form to make an ajax request, as long as you know your request setting (like url, method and parameters data).
All answers here are still using the FormData API. It is like a "multipart/form-data" upload without a form. You can also upload the file directly as content inside the body of the POST request using xmlHttpRequest like this:
var xmlHttpRequest = new XMLHttpRequest();
var file = ...file handle...
var fileName = ...file name...
var target = ...target...
var mimeType = ...mime type...
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.send(file);
Content-Type and Content-Disposition headers are used for explaining what we are sending (mime-type and file name).
I posted similar answer also here.
UPDATE (January 2023):
You can also use the Fetch API to upload a file directly as binary content (as also was suggested in the comments).
const file = ...file handle...
const fileName = ...file name...
const target = ...target...
const mimeType = ...mime type...
const promise = fetch(target, {
method: 'POST',
body: file,
headers: {
'Content-Type': mimeType,
'Content-Disposition', `attachment; filename="${fileName}"`,
},
},
});
promise.then(
(response) => { /*...do something with response*/ },
(error) => { /*...handle error*/ },
);
See also a related question here: https://stackoverflow.com/a/48568899/1697459
Step 1: Create HTML Page where to place the HTML Code.
Step 2: In the HTML Code Page Bottom(footer)Create Javascript: and put Jquery Code in Script tag.
Step 3: Create PHP File and php code copy past. after Jquery Code in $.ajax Code url apply which one on your php file name.
JS
//$(document).on("change", "#avatar", function() { // If you want to upload without a submit button
$(document).on("click", "#upload", function() {
var file_data = $("#avatar").prop("files")[0]; // Getting the properties of file from file field
var form_data = new FormData(); // Creating object of FormData class
form_data.append("file", file_data) // Appending parameter named file with properties of file_field to form_data
form_data.append("user_id", 123) // Adding extra parameters to form_data
$.ajax({
url: "/upload_avatar", // Upload Script
dataType: 'script',
cache: false,
contentType: false,
processData: false,
data: form_data, // Setting the data attribute of ajax with file_data
type: 'post',
success: function(data) {
// Do something after Ajax completes
}
});
});
HTML
<input id="avatar" type="file" name="avatar" />
<button id="upload" value="Upload" />
Php
print_r($_FILES);
print_r($_POST);
Basing on this tutorial, here a very basic way to do that:
$('your_trigger_element_selector').on('click', function(){
var data = new FormData();
data.append('input_file_name', $('your_file_input_selector').prop('files')[0]);
// append other variables to data if you want: data.append('field_name_x', field_value_x);
$.ajax({
type: 'POST',
processData: false, // important
contentType: false, // important
data: data,
url: your_ajax_path,
dataType : 'json',
// in PHP you can call and process file in the same way as if it was submitted from a form:
// $_FILES['input_file_name']
success: function(jsonData){
...
}
...
});
});
Don't forget to add proper error handling
Try this puglin simpleUpload, no need form
Html:
<input type="file" name="arquivo" id="simpleUpload" multiple >
<button type="button" id="enviar">Enviar</button>
Javascript:
$('#simpleUpload').simpleUpload({
url: 'upload.php',
trigger: '#enviar',
success: function(data){
alert('Envio com sucesso');
}
});
A non-jquery (React) version:
JS:
function fileInputUpload(e){
let formData = new FormData();
formData.append(e.target.name, e.target.files[0]);
let response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
let result = await response.json();
console.log(result.message);
}
HTML/JSX:
<input type='file' name='fileInput' onChange={(e) => this.fileInput(e)} />
You might not want to use onChange, but you can attach the uploading part to any another function.
Sorry for being that guy but AngularJS offers a simple and elegant solution.
Here is the code I use:
ngApp.controller('ngController', ['$upload',
function($upload) {
$scope.Upload = function($files, index) {
for (var i = 0; i < $files.length; i++) {
var file = $files[i];
$scope.upload = $upload.upload({
file: file,
url: '/File/Upload',
data: {
id: 1 //some data you want to send along with the file,
name: 'ABC' //some data you want to send along with the file,
},
}).progress(function(evt) {
}).success(function(data, status, headers, config) {
alert('Upload done');
}
})
.error(function(message) {
alert('Upload failed');
});
}
};
}]);
.Hidden {
display: none
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div data-ng-controller="ngController">
<input type="button" value="Browse" onclick="$(this).next().click();" />
<input type="file" ng-file-select="Upload($files, 1)" class="Hidden" />
</div>
On the server side I have an MVC controller with an action the saves the files uploaded found in the Request.Files collection and returning a JsonResult.
If you use AngularJS try this out, if you don't... sorry mate :-)

Categories

Resources