Ajax xhr onload not being called within django app - javascript

I have a Django app with user posts. I am trying to add a 'like' / voting system. I had done this through complete page refreshes through a redirect where the vote was made then redirected back to the same page with the new vote / like total updates. I then was reading about ajax and how I could update part of a page without a full reload.
let up_vote = document.getElementById("up-vote").addEventListener('click', () => {
console.log("clicked");
let xhr = new XMLHttpRequest();
xhr.open('GET', "{% url 'up-vote' %}", true);
console.log(xhr);
xhr.onload = () => {
console.log("inside");
};
console.log("outside");
});
So far my js looks like this. When I click on "up-vote", "clicked" is printed along with the xhr object. However, onload appears to never be called as "inside" is never printed but instead passes straight over to "outside".
I have a feeling that the issue is to do with the URL path but I don't know how to get path properly.
The basic file structure of this app is:
app
|-static/app
|-scripts/*this js file*/
|-images
|-styles
|-templates/app
|-html files
|-views.py *where the request is being made*
|-urls.py
urls.py contains,
urlpatterns = [
...
path('post/<int:pk>/up/', up_vote, name='up-vote'),
...
]
and the views.py contain,
#login_required()
def up_vote(request, pk):
print("HI")
obj = get_object_or_404(Post, pk=pk)
uid = request.user.id
if not obj.votes.exists(uid):
obj.votes.up(uid)
data = {
'votes': obj.votes.count()
}
return JsonResponse(data)
Any help or advice greatly appreciated :)
p.s. I have also tried xhr.onreadystate which is what gives to the thought of the URL path being wrong.

AJAX request (as you might have guessed from its name), is asynchronous, so it will really pass straight to 'outside'.
In addition to that, you have to call xhr.send()
let up_vote = document.getElementById("up-vote").addEventListener('click', () => {
console.log("clicked");
let xhr = new XMLHttpRequest();
xhr.open('GET', "{% url 'up-vote' %}", true);
console.log(xhr);
xhr.onload = () => {
console.log("inside");
};
xhr.send()
console.log("outside");
});
PS: I think your URL/views have issues. Your view and routes requires a pk, but your {% url 'up-vote' %} doesn't pass a pk to it

Related

How to handle POST form data with Django (refresh avoided)?

I'm trying to save the form's data in a database using Django. Refreshing after click on submit button is avoided using:
scripts.py
var form = document.getElementById("mail_form_id");
function handleForm(event) { event.preventDefault(); }
form.addEventListener('submit', handleForm);
function send_mailform(){
console.log("cal")
var http = new XMLHttpRequest();
http.open("POST", "", true);
http.setRequestHeader("Content-type","application/x-www-form-urlencoded");
var params = "search=" + document.getElementById('mail_input').value;
http.send(params);
http.onload = function() {
alert(http.responseText);
}
}
document.getElementById("mail_send_btn").addEventListener('click', send_mailform, false);
views.py
#Mail check
if request.POST:
Marketingform = Marketingforms(request.POST)
if Marketingform.is_valid():
receiver_mail = Marketingform.cleaned_data['receiver_mail']
p = mail_receiver(receiver_mail=receiver_mail)
p.save()
print("correct")
views.py
class mailForm(forms.ModelForm):
class Meta:
model = mail_receiver
fields =[
'receiver_mail',
]
widgets = {
'receiver_mail': forms.EmailInput(attrs={ 'id':'mail_input', 'name':'mail_input'}),
}
How can I receive the value of params in the django views.py?
First your ajax request is not going to work because of csrf token. you must have a request header with name: 'X-CSRFToken' and value of the csrftoken cookie that is in the browser cookies. You must get the csrftoken cookie value and set as the header value.
Header should look like:
http.setRequestHeader('X-CSRFToken', getCookie('csrftoken'));
And getCookie() must be function to get cookie value based on its name. Django has a clean doc about this: https://docs.djangoproject.com/en/3.0/ref/csrf/
And the answer for your question is that request object contains the post data and you can have them like:
request.POST.get('param_name')
This will return None if param_name doesn't exists.
Also its better to check like:
if request.is_ajax():instead of if request.POST:

Why is my ajax request repeating same data from Laravel controller?

Goal: load more rows from the database to a view using an ajax request when a user clicks the "load more" button. I would like the data to load without a page reload.
Problem: The data being loaded via ajax keeps repeating the same rows on every request and doesn't paginate as per standard request.
Detail: I have a view that loads 4 rows from the database which I paginate using Laravel's built-in pagination. I've added an event listener on a "load more" button which successfully sends the request to the controller, which in turn successfully returns data. The controller returns a partial view of the data I want to display. However this data doesn't seem to increment properly and keeps repeating the records shown on each request. I am not sure what I am missing here, if the problem is in the controller or in the JS?
I am not very experienced with Laravel, PHP and JS since coming from more of a web designer and UI design background and would love to really understand what I am doing wrong here.
PLEASE NO JQUERY EXAMPLES.
Partial view:
#foreach ($products as $product)
<div style="background-color:pink; width: 200px;">
<p>{{ $product->title }}</p>
<img src="/images/product/{{ $product->img }}" alt="{{ $product->title }}" style="width: 50px;">
</div>
#endforeach
Javascript:
(I am updating the button href attribute so the request URL reflects the correct query)
const container = document.querySelector('#sandbox-container');
let button = document.getElementById('load-stuff');
let url = button.getAttribute('href'); // http://127.0.0.1:8000/sandbox?page=2
let pageNum = button.getAttribute('href').substr(35,1);
button.addEventListener('click', (event) => {
event.preventDefault();
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
// if page loads successfully, replace the number at the end of the url with the incremented page number
pageNum++;
newUrl = url.replace(/page=([^d]*)/, `page=${pageNum}`);
button.setAttribute('href', newUrl);
xhr.onload = function() {
if (xhr.status === 200) {
container.insertAdjacentHTML('beforeend', xhr.responseText);
}
else {
console.log(`Request failed, this is the response: ${xhr.responseText}`);
}
};
xhr.send();
})
Controller:
public function sandbox(Request $request)
{
$products = Product::orderBy('title', 'asc')->paginate(4);
if($request->expectsJson()){
return view('sandbox-more', compact('products'));
} else {
return view('sandbox', compact('products'));
}
}
Consider this snippet for your javascript
const container = document.querySelector('#sandbox-container');
let button = document.getElementById('load-stuff');
button.addEventListener('click', (event) => {
event.preventDefault();
const xhr = new XMLHttpRequest();
let url = button.getAttribute('href');
let pageNum = button.getAttribute('data-page-number') || 0;
xhr.open('GET', url, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
// if page loads successfully, replace the number at the end of the url with the incremented page number
pageNum++;
newUrl = url + '?page=' + pageNum;
xhr.onload = function() {
if (xhr.status === 200) {
container.insertAdjacentHTML('beforeend', xhr.responseText);
button.setAttribute('data-page-number', pageNum);
}
else {
console.log(`Request failed, this is the response: ${xhr.responseText}`);
}
};
xhr.send();
})
What I've done here is to have the page number saved to a dedicated custom attribute "data-page-number". Doing "button.getAttribute('href').substr(35,1)" is inefficient. And then check the page number and increment it on the button's click event. Also, only update the "data-page-number" attribute when the request has been successful. I hope this helps
You should regenerate the pagination every time you make a request to get the correct data. Here is a very good example on doing it via jQuery. Should just adjust it to your needs since you are using pure Javascript.

jQuery AJAX call to Vanilla JS, what am I missing?

UPDATE: I failed to mention that I already read the suggested duplicate answer and it didn't get me any further as I didn't see how it related to my problem. The user ponury-kostek below, did however manage to explain it simply enough without all that clutter, for me to understand. So that's how I don't see it as a duplicate.
I'm trying to implement saving user data into a database when the user logs in with LinkedIn (to keep track of who watched my page). I found a tutorial that used jQuery, and I found a GitHub (here) page for conversion of jQuery to Vanilla JS, but I'm struggling to understand what I need to do to convert this specific statement.
I got the whole thing working using just that one line of jQuery, no problems - but I don't want to force users to load the jQuery lib!
I'll post the jQuery I'm trying to convert, the Vanilla JS solution I have so far, and the conversion "formula" suggested on the GitHub page:
jQuery I'm trying to convert:
$.post('saveUserData.php',
{
oauth_provider: 'linkedin',
userData: JSON.stringify(userData)
},
function(data){ return true; });
My attempt at a Vanilla JS solution
var theUrl = 'saveUserData.php';
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function (data) {
};
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
httpRequest.open('POST', theUrl);
httpRequest.send({oauth_provider:'linkedin',userData: JSON.stringify(userData)}, function(data){ return true; });
Error thrown:
script.js:10 Uncaught DOMException: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.
at saveUserData (http://localhost:8012/linkedCV/script.js:10:14)
at displayProfileData (http://localhost:8012/linkedCV/index.php:43:4)
at B.<anonymous> (https://platform.linkedin.com/js/framework?v=1.0.350-1429&lang=undefined:3350:17)
at B.runHandler (https://platform.linkedin.com/js/framework?v=1.0.350-1429&lang=undefined:172:9)
at B.<anonymous> (https://platform.linkedin.com/js/framework?v=1.0.350-1429&lang=undefined:3355:6)
at B.handleSuccessResults (https://platform.linkedin.com/js/framework?v=1.0.350-1429&lang=undefined:172:9)
at Object.g [as success] (https://platform.linkedin.com/js/framework?v=1.0.350-1429&lang=undefined:3243:4)
at Object.incoming (https://platform.linkedin.com/js/framework?v=1.0.350-1429&lang=undefined:817:38)
at _window_onMessage (https://platform.linkedin.com/js/framework?v=1.0.350-1429&lang=undefined:581:102)
My JS (in the index header):
<script type="text/javascript" src="//platform.linkedin.com/in.js">
api_key: thecorrectAPIkey aka 'Client ID'
authorize: true
onLoad: onLinkedInLoad
scope: r_basicprofile r_emailaddress
</script>
<script type="text/javascript">
// Setup an event listener to make an API call once auth is complete
function onLinkedInLoad() {
IN.Event.on(IN, "auth", getProfileData);
}
// Use the API call wrapper to request the member's profile data
function getProfileData() {
IN.API.Profile("me").fields("id", "first-name", "last-name", "headline", "location", "picture-url", "public-profile-url", "email-address").result(displayProfileData).error(onError);
}
// Handle the successful return from the API call
function displayProfileData(data){
var user = data.values[0];
document.getElementById("picture").innerHTML = '<img src="'+user.pictureUrl+'" />';
document.getElementById("name").innerHTML = user.firstName+' '+user.lastName;
document.getElementById("intro").innerHTML = user.headline;
document.getElementById("email").innerHTML = user.emailAddress;
document.getElementById("location").innerHTML = user.location.name;
document.getElementById("link").innerHTML = 'Visit profile';
document.getElementById('profileData').style.display = 'block';
saveUserData(user);
}
// Handle an error response from the API call
function onError(error) {
console.log(error);
}
// Destroy the session of linkedin
function logout(){
IN.User.logout(removeProfileData);
}
// Remove profile data from page
function removeProfileData(){
document.getElementById('profileData').remove();
}
</script>
GitHub conversion suggestion:
// jQuery
$.post('//example.com', { username: username }, function (data) {
// code
})
// Vanilla
var httpRequest = new XMLHttpRequest()
httpRequest.onreadystatechange = function (data) {
// code
}
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
httpRequest.open('POST', url)
httpRequest.send('username=' + encodeURIComponent(username))
Since this works perfectly as long as I use the suggested jQuery (the one I want to convert to Vanilla JS), it all works fine. So I'm going to assume the rest of the code of my page is not needed (the PHP for the DB connection and for saving user data to the DB).
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader
When using setRequestHeader(), you must call it after calling open(),
but before calling send().
var theUrl = 'saveUserData.php';
var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function (data) {
};
httpRequest.open('POST', theUrl);
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
httpRequest.send({oauth_provider:'linkedin',userData: JSON.stringify(userData)}, function(data){ return true; });

multipart HTTP request with microsoft graph javascript sdk

I'm trying to use the Microsoft Graph JavaScript SDK to create a page in OneNote with images, which OneNote requires a multipart request for. I've created a FormData object with all the data I'm trying to send.
The request goes through when I send it up myself as follows:
var xhr = new XMLHttpRequest();
xhr.open("POST", url, true);
xhr.setRequestHeader("Authorization", "Bearer" + token);
xhr.onreadystatechange = function() {
//Call a function when the state changes
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
// Request finished. Do processing here.
} else {
// handle case
}
};
// dataToSend = FormData object containing data
// (as Blobs), including the page HTML in a
// "Presentation" part as specified
xhr.send(dataToSend);
However, since I'm using the Graph SDK to make all my other requests, I'm wondering if there's a way to do the multipart request with the SDK as well. So far, this is what I've tried:
this.client
.api(pagesURL)
.version("beta")
.header("Content-Type", "text/html")
.post(dataToSend);
Investigating the request in Fiddler shows that the request body contains [object, Object], not the data formatted as a multipart request. Any help on how to get the FormData object into the request properly using the SDK/ guidance on whether this is possible would be greatly appreciated!
I believe this is what you're looking for:
this.client
.api("https://graph.microsoft.com/beta/me/notes/sections/{Section ID}/pages")
.header("Content-Type", "application/xhtml+xml")
.header("boundary", "MyPartBoundary")
.post(dataToSend);
This snippet was adapted from the multi-part unit test used by the SDK itself. You can find that test at https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/spec/types/OneNote.ts
Update the microsoft-graph-client to latest version and try something like this.
const HTMLPageContent =
`<!DOCTYPE html>
<html>
<head>
<title>A page with rendered images</title>
</head>
<body>
<p>Here is an image uploaded as <b>binary data</b>:</p>
<img src="name:imageBlock1" alt="an image on the page" />
</body>
</html>`;
let sectionId = "<Your_OneNote_Page_Section_Id>";
let formData = new FormData();
let htmlBlob = new Blob([HTMLPageContent], {
type: "text/html"
});
formData.append("Presentation", htmlBlob);
formData.append("imageBlock1", file);
client
.api(`/me/onenote/sections/${sectionId}/pages`)
.post(formData)
.then((json) => {
console.log(json);
return Promise.resolve();
});

Load HTML Table from XMLHttpRequest response

I am loading my table on document.ready() from a json file as follows
document load....
$(document).ready(function () {
getSummaryData(function (data1) {
var dataarray=new Array();
dataarray.push(data1);
$('#summaryTable').DataTable({
data: dataarray,
"columns": [
---
---
and retrieving the data from a file as follows
function getSummaryData(cb_func1) {
$.ajax({
url: "data/summary.json",
success: cb_func1
});
console.log(cb_func1)
}
This was essentially loading dummy data so i could I could figure out how to load the table correctly etc. This works fine.
It does following
1. page loads
2. reads data from file
3. populates table
In reality, the data will not be loaded from file but will be returned from xhr response but I am unable to figure out
how to wire it all together. The use case is
POST a file via XMLHttpRequest
Get response
populate table (same data format as file)
I will post the file as follows...
<script>
var form = document.getElementById('form');
var fileSelect = document.getElementById('select');
var uploadButton = document.getElementById('upload');
---
form.onsubmit = function(event) {
event.preventDefault();
---
---
var xhr = new XMLHttpRequest();
// Open the connection.
xhr.open('POST', 'localhost/uploader', true);
// handler on response
xhr.onload = function () {
if (xhr.status === 200) {
console.log("resp: "+xhr);
console.log("resptxt: "+xhr.responseText);
//somehow load table with xhr.responseText
} else {
alert('ooops');
}
};
// Send the Data.
xhr.send(formData);
So ideally I need one empty row in the table or similar until someone uploads a file and then the table gets populated with the response.
Any help much appreciated.
var xhr1 = new XMLHttpRequest();
xhr1.open('POST', "youruploadserver.com/whatever", true);
xhr1.onreadystatechange = function() {
if (this.status == 200 && this.readyState == 4) {
console.log(this.responseText);
dostuff = this.responseText;
};//end onreadystate
xhr1.send();
It looks mostly correct, You want the this.readyState == 4 in there. what is your question, how to populate a table from the response?
That also depends on how you are going to send the data and how the server is going to parse the data, looks like you want to use a json format which is smart. JSON.stringify(formdata) before you send it and then make sure your server parses it as a json object Using body-parser depending on what server you are using. and then you JSON.stringify() the object to send it back.

Categories