jquery ajax request to nodejs server only works on page refresh - javascript

I am quite new to AJAX and I am not sure what I am doing wrong. I have a webpage that fetches all comments on a post with an AJAX get request. The issue is that the AJAX request is only successful after the webpage is refreshed. I disabled the cache to see if that would solve the issue, but it didn't.
For example, when I fetch the first comments after refreshing the page from post A and then go onto post B on the website, the comments from the post A appear as the comments for post B, then when I refresh the page the comments for post B are replaced with post B's comments successfully.
I am using jQuery to make the request:
$.ajax({
type: "GET",
url: someURL,
success: (comments) => {
console.log(comments);
comments.questions.forEach(questionComment => {
$('.questionComments').append(
`<div class="comment">
<p>${questionComment.content}</p>
</div>
`
)
});
comments.answers.forEach(answer => {
answer.forEach(answerComment => {
$(`.answer#${answerComment.forId} .answerComments`).append(
`<div class="comment">
<p>${answerComment.content}</p>
</div>
`
)
})
})
},
cache: false
})
Server-Side: (express.js, mongoose)
let allComments = {}
app.get('/questions/:questionID/getComments', (req, res) => {
if (err) return console.error(err)
Comment.find({ forQuestion: true, forId: req.params.questionID }, (err, questionComments) => {
allComments['questions'] = questionComments
})
Answer.find({ questionId: req.params.questionID }, (err, answers) => {
if (err) return console.error(err);
allAnswerComments = []
answers.forEach(answer => {
Comment.find({ forAnswer: true, forId: answer._id }, (err, comments) => {
allAnswerComments.push(comments)
})
});
allComments['answers'] = allAnswerComments
})
res.send(allComments)
})
What the commets object looks like before the reload - a blank object
What the comments object looks like after the reload
When you navigate to a different post / different URL, the object from the previous post / URL is the initial object on the new post, and then when you reload the page the correct object is fetched.

According to your description, the AJAX request works when executed, but the problem is that it's executed only once, at page load. Let's write a function for this purpose:
function sendAJAX() {
$.ajax({
type: "GET",
url: someURL,
success: (comments) => {
console.log(comments);
comments.questions.forEach(questionComment => {
$('.questionComments').append(
`<div class="comment">
<p>${questionComment.content}</p>
</div>
`
)
});
comments.answers.forEach(answer => {
answer.forEach(answerComment => {
$(`.answer#${answerComment.forId} .answerComments`).append(
`<div class="comment">
<p>${answerComment.content}</p>
</div>
`
)
})
})
},
cache: false
})
}
And ensure that it's called periodically:
sendAJAX();
seInterval(sendAJAX, 10000);
This will still be incorrect, because it will add all comments every ten seconds. To improve this, you could add a value representing the moment of the last request and on server-side load only the comments that were created between that moment and the current one.

Can you please describe your working on get parameters how are you setting it.Is there any cookie involved there? Telling the get comment will definitely help us to figure our the problem how ever sendAjax() function will be great idea instead of writing ajax in open Script.
On other side I will never recommend you seInterval(sendAJAX, 10000); instead install a service worker and hit it back from your API when new comment is added. And then it should only fetch the last one and put it in your comment section.

As per your description it seems that the main problem is comment of Post A are appearing in comment of Post B.
Now, there is minimal data provided and by looking and your code, I am assuming that you are not refreshing page programmatically when switching from Post A to Post B.
So, the root cause of your problem is append. You are appending data to your div's. So, before your foreach loops, first clear the html using
$('.questionComments').html("")
And for the answers replace it as below
comments.answers.forEach(answer => {
let first = true;
answer.forEach(answerComment => {
if(first) {
$(`.answer#${answerComment.forId} .answerComments`).html("")
first = false;
}
$(`.answer#${answerComment.forId} .answerComments`).append(
`<div class="comment">
<p>${answerComment.content}</p>
</div>
`
)
})
})
The above snippet is just to demonstrate that you have to identify when the first comment is added and then reset it.
I haven't tested the snippet but I hope you got an idea what is wrong in the code.

Related

How to correctly use Fetch in JavaScript and Django?

I am trying to make a METAR decoder as shown:
I am using fetch in Vanilla js and I plan to send the entered code to a Django view. From the Django view, the decoded data will be taken and displayed in the template.
views.py
def ToolsPageView(request):
if request.method == "POST":
jsonData = json.loads(request.body)
metarCode = jsonData.get('Metar')
return JsonResponse("Success", safe=False)
return render(request, 'app/tools.html')
urls.py
...
path("tools", views.ToolsPageView, name="tools")
tools.html
<div class="metar-code-decode">
<form method="POST" action="{% url 'tools' %}" id="metar-form">
{% csrf_token %}
<input type="text" placeholder="Enter METAR: " id="metar-value"> <br>
<input type="submit" id="metar-button">
</form>
</div>
tool.js
function getDecodedMetar() {
let formButton = document.querySelector("#metar-button");
formButton.onclick = function (e) {
let metarCode = document.querySelector("#metar-value").value;
sendMetar(metarCode);
//e.preventDefault();
//getMetar(metarCode);
};
}
function sendMetar(metarCode) {
fetch('/tools', {
method: "POST",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
body: JSON.stringify({
Metar: metarCode,
}),
});
}
I have used the same code for POST using fetch where I had to let user update his/her profile. And that worked. But, this does not work and the error keeps on changing from time to time after restarting the server. At the first try, there was no error produced and the server also showed a POST request being made. And the latest error which I am getting is "In order to allow non-dict objects to be serialized set the safe parameter to False." I get the same thing again and again even after setting safe=False within the JsonResponse(). Worth to note, request when converted to request.json() gives an error.
Am I using fetch wrongly? If yes, what is the correct way?
I'm not sure you have the flow right. The idea is that the button, when clicked, will call a function (fetch) that will send data to the view, which will decode it and send it back to the JavaScript, so that it could be displayed without reloading the entire page.
I think this might help:
let formButton = document.querySelector("#metar-button");
// When the button is clicked,
formButton.onclick = function(e) {
// do NOT send the form the usual way
e.preventDefault();
let metarCode = document.querySelector("#metar-value").value;
// Run the function that will send the code to the ToolsPageView
sendMetar(metarCode);
}
async function sendMetar(metarCode) {
const response = await fetch('/tools', {
method: "POST",
headers: {
"X-CSRFToken": getCookie("csrftoken"),
},
body: JSON.stringify({
'Metar': metarCode,
}),
})
.then(response => response.json())
.then(data => {
console.log(data);
// extract the decoded value from the data sent back from the view
// display it by targeting the element in your html that you want
// to display it
});
}
And in your view,
def ToolsPageView(request):
if request.method == "POST":
jsonData = json.loads(request.body)
metarCode = jsonData.get('Metar')
# Remove the original JsonResponse
# return JsonResponse("Success", safe=False)
# and INSTEAD,
# Send the code back to the JavaScript
# I don't THINK you need safe=False here?
return JsonResponse({'MetarCode': metarCode})
return render(request, 'app/tools.html')

How can we ignore the openHash value in cy.route() request send

In Cypress I am using cy.route() for sending the below request, but cypress is not identifying the below request send. In the route url there is a openHash value which will be different for every POST request. Is there any way to ignore the openHash value or accept what ever value displays there.
So far I have tried by giving the url in following ways in route.
url: '**/student/details.php?viewDetails=project&stdCount=1&sectionID=1&openHash=**',
url: '**/student/details.php?viewDetails=project&stdCount=1&sectionID=1&openHash=**&ajaxCall=true**',
I believe while using cy.route() the POST url need to match exactly. Could someone please advise
Cypress version: 5.4.0
Student.feature
Feature: Update student details
Background: User logged in to application
Given I should load all of the routes required for tests
Scenario: Update student details
When I am logged in as the student user
And I click on "Student" subtab
And I should see details displayed
Step definition:
import { Then, When, And } from "cypress-cucumber-preprocessor/steps";
before(() => {
Then('I should load all of the routes required for tests', () => {
cy.server();
cy.route({
method: 'POST',
url: '**student/details.php?viewDetails=project&stdCount=1&sectionID=1&openHash=5fc8329a76e73&ajaxCall=true**',
delay: 2000
}).as('getStudentTabDetails');
})
})
Then('I am logged in as the student user', () => {
cy.get('[name=loginUsername]').type("Student1");
cy.get('[name=loginPassword]').type("somePassword1", { sensitive: true });
cy.contains('Login').click();
})
Then('I click on {string} subtab', (student) => {
cy.get('#main a').contains(student).click({force:true});
});
Then('I should see details displayed', () => {
cy.wait('#getStudentTabDetails', { timeout: 5000 });
});
Error:
CypressError
Timed out retrying: cy.wait() timed out waiting 5000ms for the 1st request to the route: getStudentTabDetails. No request ever occurred.
Cypress.minimatch is a tool that can be used for checking the route matchers.
By default Cypress uses minimatch to test glob patterns against request URLs.
If you’re struggling with writing the correct pattern you can iterate much faster by testing directly in your Developer Tools console.
The two routes you show in the question actually pass the minimatch test.
const url = 'http://example/student/details.php?viewDetails=project&stdCount=1&sectionID=1&openHash=5fc8329a76e73&ajaxCall=true';
const pattern1 = '**/student/details.php?viewDetails=project&stdCount=1&sectionID=1&openHash=**';
console.log( Cypress.minimatch(url, pattern1) ); // true
const pattern2 = '**/student/details.php?viewDetails=project&stdCount=1&sectionID=1&openHash=**&ajaxCall=true**';
console.log( Cypress.minimatch(url, pattern2) ); // true
Here is a Cypress fiddle that shows how to use the new intercept method to handle query parameters.
/// <reference types="#cypress/fiddle" />
const test = {
html: `
<p class="text-lg"></p>
<script>
setTimeout(() => {
const url = 'http://example/student/details.php?viewDetails=project&stdCount=1&sectionID=1&openHash=5fc8329a76e73&ajaxCall=true';
window.fetch(url, { method: 'POST'});
}, 1000);
</script>
`,
test: `
cy.intercept({
method: 'POST',
url: '/student/details.php',
query: {
viewDetails: 'project', // whatever query parts you care about
stdCount: '1',
sectionID: '1'
}
}, {}) // Added an empty stub here, as my url does not actually exist
.as('getStudentTabDetails');
cy.wait('#getStudentTabDetails')
`
}
it('', () => {
cy.runExample(test)
});
The POST is made with native fetch(), which would not be captured in the old cy.route() method without using a polyfill.

Can a button both redirect and perform a function with asynchronous JavaScript

I have reviewed a few similar posts on here but everything I have found so far is using href to redirect to a totally new webpage.
I have a button in my JavaScript code which uses the Material-UI <Link>
<Button component={Link} to="/ratings" className={classes.addImage} onClick={this.submitScore}>
This button both redirects the webpage and calls a function which initiates a server request. Now this code seemed to be running fine, both redirecting and running its onClick function. However I recently discovered that running my code when not on WiFi often results in my image upload request to be ignored. I've been trouble shooting this for a little while and my current theory is the cell data is slower and the page is rerouting before the upload request gets called.
Although this doesn't quite make sense to me. It seems like the code should either run after the Link is called or not, but what I am seeing is the image upload request work while on WiFi but work infrequently while on cell data.
Any thoughts would be appreciated.
Here is the onClick function if it is helpful:
submitScore = () => {
console.log(this.state)
this.props.dispatch({type: 'SUBMIT_SCORE', payload: {ratings: this.props.scores, user_id : this.props.user.id, name: this.state.beerName, url: this.state.filename, notes: this.state.notes, filename: `${this.props.user.id}_${Date.now()}`}})
// this.props.dispatch({type: 'ADD_PICTURE', payload: {picture: this.state.imgSrc, filename: `${this.props.user.id}_${Date.now()}`}})
axios.get('/picture', {params: {filetype: this.state.imageType, filename: this.state.filename}})
.then(response =>{
var signedUrl = response.data;
console.log(response)
console.log(signedUrl)
var headers= {
'Content-Type': this.state.imageType,
};
console.log(...this.state.imageType)
return axios.put(signedUrl, this.state.newfile, {headers:headers});
})
.then(function (result) {
console.log(result,'success');
})
.catch(function (err) {
console.log(err, 'fail');
});
swal({
title: "Good job!",
text: "You clicked the button!",
icon: "success",
timer: 2000,
});
}
Instead of linking to a new page in your button, can you set it up so you redirect on success?
.then(function (result) {
console.log(result,'success');
window.location.assign('/ratings'); // <-- Redirect here
})
You would need to remove the link from your button.

Getting PUT routes to work in Angular

I'm seeking some wisdom from the Angular community. I am working on a simple project using the MEAN stack. I have set up my back-end api and everything is working as expected. Using Postman, I observe expected behavior for both a GET and PUT routes to retrieve/update a single value - a high score - which is saved in it's own document in its own collection in a MongoDB. So far so good.
Where things go off track is when trying to access the PUT api endpoint from within Angular. Accessing the GET endpoint is no problem, and retrieving and displaying data works smoothly. However, after considerable reading and searching, I am stll unable to properly access the PUT endpoint and update the high score data when that event is triggered by gameplay. Below are the snippets of code that I believe to be relevant for reference.
BACK-END CODE:
SCHEMA:
const _scoreSchema = {
name: { type: String, required: true },
value: { type: Number, "default": 0 }
};
ROUTES:
router
.route('/api/score/:highScore')
.put(scoreController.setHighScore);
CONTROLLER:
static setHighScore(req, res) {
scoreDAO
.setHighScore(req.params.highScore)
.then(highScore => res.status(200).json(highScore))
.catch(error => res.status(400).json(error));
}
DAO:
scoreSchema.statics.setHighScore = (value) => {
return new Promise((resolve, reject) => {
score
.findOneAndUpdate(
{"name": "highScore"},
{$set: {"value": value} }
)
.exec(function(err, response) {
err ? reject(err)
: resolve(response);
});
});
}
ANGULAR CODE:
CONTROLLER:
private _updateHighScore(newHighScore): void {
console.log('score to be updated to:', newHighScore)
this._gameService
.updateHighScore(newHighScore);
}
SERVICE:
updateHighScore(newHighScore: Number): Observable<any> {
console.log(newHighScore);
let url = '/api/score/' + newHighScore;
let _scoreStringified = JSON.stringify({value: newHighScore});
let headers = new Headers();
headers.append("Content-Type", "application/json");
return this._http
.put(url , _scoreStringified, {headers})
.map((r) => r.json());
}
Note that the console.log(newHighScore) in the last block of code above correctly prints the value of the new high score to be updated, it's just not being written to the database.
The conceptual question with PUT routes in angular is this: If the api is already set up such that it receives all the information it needs to successfully update the database (via the route param) why is it required to supply all of this information again in the Angular .put() function? It seems like reinventing the wheel and not really utilizing the robust api endpoint that was already created. Said differently, before digging into the docs, I naively was expecting something like .put(url) to be all that was required to call the api, so what is the missing link in my logic?
Thanks!

AngularFire2 Firebase update object without causing other subscriptions to update

I just have a question to find out if this is possible:
So what I am doing is when I submit a post I wait for it to complete then update the user object in firebase to insert a time-stamp.
This is fine and works but when the time-stamp is inserted it is causing other subscriptions that are subscribed to changes in the user object to update.
What I want to do it update the user object without causing other subscribers to be updated.
Here is where I am updating the timestamp:
I tried commenting out this line of code which stops the data duplication issue on screen but I need this code to run as I need to update the timestamp when a post is submitted.
this.af.database.object('users/' + x[0].uid + '/lastPostAt').set(timestamp)
.then(x => { this.dialogsService.showSuccessDialog('Post Submitted'); });
Here is where I am subscribing to all the posts:
subscribeAllPosts(): Observable<Post[]> {
return this.af.database.list('/posts')
.map(Post.fromJsonList);
}
Here is where I am creating the array of posts in my constructor to display via a loop in the html:
this.activeItem = this.items[0];
this.postsService.subscribeAllPosts()
.subscribe(posts => {
let container = new Array<PostContainer>();
for (let post of posts) {
this.getEquippedItemsForUsername(post.username).subscribe(
x => {
try {
container.push(new PostContainer(post, x[0].equippedItems));
} catch (ex) { }
}
);
}
this.postContainers = container;
});
In the inner subscription it gets the equippedItems for the user of the post:
getEquippedItemsForUsername(username: string) {
return this.usersService.subscribeUserByUsername(username);
}
Which in turns calls:
subscribeUserByUsername (username: string) {
return this.af.database.list('users' , {
query: {
orderByChild: 'username',
equalTo: username
}
});
}
In the HTML it loops through the postContainer[]:
<li *ngFor="let item of postContainers">
So all that works the issue as stated is that if I do not have the following commented out then the posts will be duplicated more and more as the posts are submitted. If I refresh the app then the posts will show the correct non duplicated posts until another post is submitted.
this.af.database.object('users/' + x[0].uid + '/lastPostAt').set(timestamp)
.then(x => { this.dialogsService.showSuccessDialog('Post Submitted'); });
EDIT: Solved by splitting up logic.

Categories

Resources