Ajax POST to Flask not working after page refresh - javascript

I'm improving a system for controlling a telescope remotely. A Raspberry Pi runs flask, and provides a video stream for a camera attached to the telescope. The telescope's focuser is actuated by a stepper motor controlled with an Arduino.The server provides a website that shows the video stream, and offers two buttons to move the focuser in and out.
When either button is clicked, the client sends a POST to the RasPi, and then the RasPi tells the Arduino to move the focuser. But crucially I did not want the page to refresh while refocusing. Hence, I used jQuery and Ajax to suppress the page refresh.
The relevant code snippets are here:
Python/Flask code:
#app.route('/stream/<wcam>', methods=['GET'])
def stream_get(wcam):
class FocuserForm(FlaskForm):
nsteps = IntegerField('# steps: ', default=1)
focuser_in = SubmitField('Focuser in')
focuser_out = SubmitField('Focuser out')
form = FocuserForm()
return render_template('stream.html', wcam=wcam, form=form)
#app.route('/stream/<wcam>', methods=['POST'])
def stream_post(wcam):
results = request.form
arduino_serial = SerialFocuser()
if results['caller'] == "focuser_in":
command = "MVD" + results['steps'] + "\n"
arduino_serial.send_command(command)
elif results['caller'] == "focuser_out":
command = "MVU" + results['steps'] + "\n"
arduino_serial.send_command(command)
return ''
Web (stream.html):
<html>
<head>
<title>Video Streaming</title>
<style>
...
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
$(document).ready(function() {});
</script>
</head>
<body>
<h1>Streaming camera {{ wcam }}</h1>
<br>
<img id="bg" src="{{ url_for('video_feed', wcam=wcam) }}", height="480" width="640">
Back
<br>
<!--######################################################-->
<!--# Focuser handling -->
<!--######################################################-->
<br>
<form id="flaskform" method="POST">
<p>
{{ form.nsteps.label }} {{ form.nsteps() }}
{{ form.focuser_in() }}
{{ form.focuser_out() }}
</p>
</form>
<script>
// $(document).ready(function() { // Moved to header
var form = document.getElementById('flaskform');
function onSubmit(event) {
console.log('onSubmit function');
var objectID = event.explicitOriginalTarget.id;
var nsteps = form.nsteps.value;
var return_data = {caller: "", steps: nsteps};
if (objectID == "focuser_in") {
return_data.caller = objectID;
console.log("Focuser_in detected");
} else if (objectID == "focuser_out") {
return_data.caller = objectID;
console.log("Focuser_out detected");
} else if (objectID == "nsteps") {
console.log("nsteps detected");
event.preventDefault();
return;
} else {
console.log("No matches");
return;
}
console.log("About to run Ajax");
$.ajax({
url: "stream.html",
type: "post",
data: return_data,
success: function(response) {
console.log('It worked!');
},
error: function(xhr, status, text) {
console.log('An error occurred:', status,"; ", text);
},
timeout: 1000 // 1s
}); // Ajax
console.log("After running Ajax");
if (event) { event.preventDefault(); }
}
// prevent when a submit button is clicked
form.addEventListener('submit', onSubmit, false);
//<!--form.addEventListener('submit', onSubmit, false);-->
// prevent submit() calls by overwriting the method
form.submit = onSubmit;
//}); // Moved to header
</script>
</body>
</html>
The problem is as follows:
If I refresh the page on the client's browser and then click a button, ajax does POST, but flask does not seem to receive it. The request times out.
If I now restart the server (I'm developing this with PyCharm, so I just click re-run) without refreshing the page in the client, and then click a button, flask does get the POST, and the focuser works like a charm.
If I refresh the page again, then the buttons stop working until I reset the server.
Why does this happen? Obviously the code works in its main purpose, but somehow the page refresh is breaking something.

I had a similar issue once with a camera thread blocking all calls. When you reset the server, does your camera feed still run (before clicking the button)?
Because basically you are calling your camera feed twice - first with the get call when you refresh your page, then again with the post call.
I'd advice you to refactor your submitted code into an alternative function for clarity:
#app.route('/stream/<wcam>', methods=['POST'])
def moveCommand:
if form.is_submitted():
# POST method
results = request.form
arduino_serial = SerialFocuser()
if results['caller'] == "focuser_in":
command = "MVD" + results['steps'] + "\n"
arduino_serial.send_command(command)
elif results['caller'] == "focuser_out":
command = "MVU" + results['steps'] + "\n"
arduino_serial.send_command(command)
So basically you keep your get method for only the streaming and use the post for the moving around.

Thanks to #Peter van der Wal for pointing me towards the solution.
The video streamer has a while True loop, which continually takes frames from the camera, hence locking the thread.
The solution was to start the app with the threaded option on:
Before:
app.run(host='0.0.0.0', debug=True)
Now:
app.run(host='0.0.0.0', debug=True, threaded=True)
This allows the video streaming thread to continue on its own, while allowing the server to process other commands.

Related

Django Admin Page execute javascript on Custom Action Buttons

I'm trying to add a custom button in each row next to a user to the django admin page which makes it easier for the admin to click the button and generate a password reset link for a particular user rather than navigate to the hidden actions section of the django admin page.
Something like this - https://hakibenita.com/how-to-add-custom-action-buttons-to-django-admin
Following is the code I've implemented and it works and navigates to a new page with the correct reset-password request and the reset_password() method executes with a link being sent to the user.
However, when I click the button, I would like to send an AJAX GET request to the same url as above and just a show an alert to the admin on request completion instead of navigating to a new page. Currently I'm unable to run the javascript code using the format_html method in Django (see code below)
class UserAdmin(Admin.modelAdmin):
list_display = ('name', 'email', 'custom_actions')
form = forms.UserAdminForm
def get_urls(self):
urls = super().get_urls()
custom_urls = [
url(
r'reset-password/(?P<user_id>.+)',
self.admin_site.admin_view(self.reset-password),
name='reset-password',
),
]
return custom_urls + urls
def custom_actions(self, obj):
user = user_model.User.get(user_id=obj.id)
password_reset_url = reverse('admin:reset-password', args=[user.id])
return mark_safe(format_html(
f'<a class="button" onclick="parent.location=\'{password_reset_url}\'" >Reset Password</a> '
custom_actions.short_description = 'Custom Actions'
custom_actions.allow_tags = True
def reset_password(self, request, password_reset_id):
password_reset.send_password_reset_link(password_reset_id=password_reset_id)
return HttpResponse(status=200)
Below is the HTML/JS code that I was testing for the behavior I want on the page and works, I was hoping to the stitch the same into the above Django code but Django cannot execute and just shows the script as a string on the admin page.
<button id='jax' class="button">AJAX</button>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
$(document).ready(function() {
$("#jax").click(function(){
$.ajax({
type : "GET",
url : "my-dynamic-url",
timeout:10,
jsonp : "jsonp",
success : function (response, textS, xhr) {
console.log('oof')
alert("Password reset link has been resent");
},
error : function (xmlHttpRequest, textStatus, errorThrown) {
if(textStatus==='timeout')
alert("request timed out");
}
});
});
});
</script>
Is there a correct way to integrate the above javascript code on admin page on the click of the button?
Are you missing out on commas? From an example according to the documentation:
format_html("{} <b>{}</b> {}",
mark_safe(some_html),
some_text,
some_other_text,
)
Maybe you should wrap {password_reset_url} in commas and see if that works.
If this does not solve your problem, elaborating on this will be helpful:
Django cannot execute and just shows the script as a string on the admin page.
Which script are you referring to?
The solution:
Python:
class UserAdmin(Admin.modelAdmin):
list_display = ('name', 'email', 'custom_actions')
form = forms.UserAdminForm
def get_urls(self):
urls = super().get_urls()
custom_urls = [
url(
r'reset-password/(?P<user_id>.+)',
self.admin_site.admin_view(self.reset-password),
name='reset-password',
),
]
return custom_urls + urls
def custom_actions(self, obj):
user = user_model.User.get(user_id=obj.id)
password_reset_url = reverse('admin:reset-password', args=[user.id])
return mark_safe(format_html(
f'<a class="button" onclick="send_password_link(\'{password_reset_url}\'") >Reset Password</a> '
custom_actions.short_description = 'Custom Actions'
custom_actions.allow_tags = True
def reset_password(self, request, password_reset_id):
password_reset.send_password_reset_link(password_reset_id=password_reset_id)
return HttpResponse(status=200)
HTML:
This should be under base-app/templates/your-app/change_list.html
{% extends "admin/change_list.html" %}
{% load static %}
{% block content %}
<script src="{% static 'js/passwordReset.js' %}"></script>
<!-- Render the rest of the ChangeList view by calling block.super -->
{{ block.super }}
{% endblock %}
Javascript:
This should be under base-app/static/static/js/passwordReset.js
function send_password_link(url) {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
alert('Password Reset Sent');
}
};
xhttp.open("GET", url, true);
xhttp.send();
}

How to prevent a fetch request from reloading the page in Javascript when there is not a button?

Let me preface this by saying there are endless threads that describe the issue WITH a button involved. Usually it's solved by just calling event.preventDefault() on the event you pass in. But what if the post request is called after something happens that isn't within the user's control, for instance, after a certain amount of frame?
makeScore (userId, gameId, time) {
fetch('http://localhost:3000/scores', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
time: time,
user_id: userId,
maze_id: gameId
})
}).catch(error => console.log('ERROR'))
};
loadNextLevel(option) {
console.log("Load Next");
// Making a score
clearInterval(this.interval);
this.time = document.getElementById('timer_text').innerHTML;
this.makeScore(this.userId, this.gameId, this.time);
console.log("Score made");
if (this.GAMESTATE != GAMESTATE.GAMEOVER) {
console.log(this.gameObjects);
document.getElementById('timer_text').innerHTML = "Time"
this.gameObjects = []
this.gameIndex += 1;
console.log(this.gameIndex, this.maxMazes);
if (option == "new") {
this.gameIndex = 0;
}
}
if (this.gameIndex >= this.maxMazes) {
this.gameEnd();
return;
}
With this code, I want to call makeScore only after loadNextLevel is called, and that is only getting called when a level is completed. But, currently the score is saved, and the page is refreshed. How can I prevent this refresh?
Edit: added scores controller below
class ScoresController < ApplicationController
def index
scores = Score.all
render json: scores
end
def show
score = Score.find(params[:id])
render json: score
end
def create
if (params[:user_id] && params[:maze_id])
user = User.find_by(id: params[:user_id])
maze = Maze.find_by(id: params[:maze_id])
score = user.scores.build(time: params[:time], username: user.username)
maze.scores << score
end
end
end
Edit #2 - Adding the function that it apparently gets stuck on after the fetch is completed.
function setLoggedOutUI() {
console.log("Log out UI set");
document.getElementById("timer_text").style.display = "none";
document.getElementById("logInButton").innerHTML = "Log in";
document.getElementById("userInfo").innerHTML = "Please Log in to play";
document.getElementById("logInField").style.display = "inline-block";
document.getElementById("play").innerHTML = "Log in";
document.getElementById("scores").style.display = "none";
document.getElementById("usernameText").style.display = "inline";
uiSwitch = false;
}
I had this same issue, in my case the problem was caused by the live server. I had in the same folder the client (HTML,CSS, JS, etc.) and the database (I was using the json-server extension). The problem was that the live server was detecting the changes in the db.json file. When fetch post or delete data, it triggers the live reload in the live server extension. So I solved it by dividing the folders and opening them separately in vs code, then, inside the client folder, run live server and it worked perfectly. More details here

Trouble using res.end() in a node server

so, below is a code snippet from my server.js file. When running, and I send a URL with a message, the res.end() causes the view to render a blank page.
When I comment out the res.end() command, the view displays all of the messages, but the browser waits and waits for the signal that the response from the server is complete.
I get that you can use res.end() and put data in the parens, to be transmitted and rendered by the view.
What I expect to happen is that with no args, it will just leave the view alone, but the empty args in the parens is manifesting as an empty view.
How do I indicate that the response is complete without deleting the data on the view?
server.js
var http = require('http'),
url = require('url'),
fs = require('fs');
var messages = ["testing"];
var clients = [];
http.createServer(function(req,res) {
var url_parts = url.parse(req.url);
console.log(url_parts);
if(url_parts.pathname == '/') {
// file serving
fs.readFile('./index.html', function(err, data) {
// console.log(data);
res.end(data);
});
} else if(url_parts.pathname.substr(0,5) == '/poll'){
//polling code
var count = url_parts.pathname.replace(/[^0-9]*/,'');
console.log(count);
if(messages.length > count){
res.end(JSON.stringify({
count: messages.length,
append: messages.slice(count).join("\n")+"\n"
}));
} else {
clients.push(res);
}
} else if(url_parts.pathname.substr(0, 5) == '/msg/') {
// message receiving
var msg = unescape(url_parts.pathname.substr(5));
messages.push(msg);
while(clients.length > 0) {
var client = clients.pop();
client.end(JSON.stringify({
count: messages.length,
append: msg+"\n"
}));
}
// res.end(); //if left in, this renders an empty page, if removed,
// client keeps waiting....
}
}).listen(8080, 'localhost');
console.log('server running!');
index.html
<html>
<head>
<script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script>
var counter = 0;
var poll = function() {
$.getJSON('/poll/'+counter, function(response) {
counter = response.count;
var elem = $('#output');
elem.text(elem.text() + response.append);
//elem.text(counter);
poll();
});
}
poll();
</script>
</head>
<body>
<textarea id="output" style="width: 90%; height: 90%;">
</textarea>
</body>
</html>
I have looked in the docs, but I don't see anything specific about using .end() method with empty args to signify and end without passing data to be rendered. I have googled this, but I don't have an answer yet.
Do a res.json({success:"true"}) instead. The reason being is because res.end inherently thinks the client was sent a view prior to the stream being closed. With res.json() you can send any generic data, without an implied view being expected as well as close out the stream on client and server side.
Move res.end() inside while loop
while (clients.length > 0) {
var client = clients.pop();
client.end(JSON.stringify({
count : messages.length,
append : msg + "\n"
}));
if(!clients.length) {
res.end();
}
}
My understand of your problem is:
You have an HTML page (index.html), which has a textarea displaying all messages submitted by user. After one message is received and displayed, it will send the request for next message immediately (/poll/<n>).
To accept user's input for latest message, you open an API (/msg/<message>). When an HTTP request is sent to this API, server will extract the message, and return this message to /poll/<n> sent in step 1.
However, as HTML page (index.html) and the request to /msg/<message> happens in the same browser window, you can't let the http handler of /msg/<message> in node.js invoke res.end(), because in that case, the browser window will render the HTTP response of /msg/<message> request (blank page). Actually, you can't make the res return 200 OK, whatever data it returns. You can't make res fail the /msg/<message> request either (using req.destroy()), because in that case the browser window will render a failure/broken page, which is worse.
Unfortunately, you can't make res of /msg/<message> in node.js keep pending either. Although it will update index.html, the browser window will keep waiting...
The root cause of your problem is: browser window resource conflict between index.html and /msg/<message> response -- as long as /msg/<message> request is sent by using index.html window's URL bar, whenever its response is sent back, the window content (index.html) will be cleaned.
One solution is: using Ajax to send /msg/<message>. In this way, there would be no conflict for window resource. Example code is listed below:
<body>
<textarea id="output" style="width: 90%; height: 90%;">
</textarea>
<div>
<input type="text" id="msg">
<button type="button" onclick="submitMsg()">Submit</button>
</div>
</body>
window.submitMsg = function() {
var msg = $('#msg').val();
$.getJSON('/msg/' + msg, function(res) {
$('#msg').val('');
console.log('message sent.');
});
}
EDIT:
Another simpler solution is: open index.html in one browser window, and open /msg/<message> in another one (use res.end('message received successfully') to indicate message receiving result).

PHP Like button counter in a file.txt

I would like to recreate several like button that saves count in a file.txt but that doesn't work :/
<?php
function getClickCount()
{
return (int)file_get_contents("counter.txt");
}
function incrementClickCount()
{
$counter = getClickCount() + 1;
file_put_contents("counter.txt", $counter);
}
?>
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<script type="text/javascript">
var clicks = 0;
function onClick() {
clicks = 1;
document.getElementById("clicks").innerHTML = clicks;
};
</script>
<button type="button" onClick="onClick()" title="Vous aimez la couverture?" class="btn"><img id="heart" src="https://trello-attachments.s3.amazonaws.com/568304b85fa72dcb958a1edf/584acfc48b82595af77f2030/6257bf1efec79d5baf22309f8f327ce5/favorite.png" /></button>
<p><a id="clicks"><?php echo getClickCount(); ?></a></p>
DEMO HERE
Thanks in advance for your help, I am looking since days on the web to find it but I don't...
Alexander
counter.php
<?php
function getClickCount() {
return (int)file_get_contents("counter.txt");
}
function incrementClickCount() {
$counter = getClickCount() + 1;
file_put_contents("counter.txt", $counter);
}
if(!empty($_POST)) {
if($_POST['click'] == 'true') {
incrementClickCount();
echo getClickCount();
} else {
echo getClickCount();
}
}
?>
counter.txt
0
index.php
<html>
<head>
<title>Click Counter</title>
</head>
<body>
<button type="button" onClick="onClick()" title="Vous aimez la couverture?" class="btn"><img id="heart" src="https://trello-attachments.s3.amazonaws.com/568304b85fa72dcb958a1edf/584acfc48b82595af77f2030/6257bf1efec79d5baf22309f8f327ce5/favorite.png" /></button>
<p><a id="clicks"></a></p>
<script>
function onClick() {
loadClicks(true);
}
function loadClicks(isClicked) {
var click = isClicked === true ? true : false;
$.ajax({
url: 'counter.php',
type: 'POST',
data: {
'click': click
},
success: function(response) {
$('#clicks').text(response);
}
});
}
loadClicks(false);
</script>
</body>
</html>
Code Explanation
Whenever you click the button, there is an ajax request sent asynchronously in the background to counter.php. This PHP file receives request and process accordingly.
Here in the code, we send a single data to the PHP file in the ajax POST request which is a boolean data that is set based on the condition like if the button is clicked.
In PHP file, you will check a condition if the request is happened by button click or else other. If it is by button, you will increment the click and send the click counter value in response else you will only send the value.
You will notice I've called loadClicks function with the parameter true in onClick function and false outside the function meaning that I first call the loadClicks(false) as soon as the script is started its execution to load only the clicks value and later when I click the button loadClicks(true) is invoked meaning increment and fetch the value.
You will understand the code when you go through them carefully.
At first glance, I see 3 problems with your script.
1) You are mixing JavaScript and PHP. JavaScript runs on browsers and PHP runs on servers. If you want to exchange data between those parts of your script you need to make a server call from the JS part to the server, e.g. by using AJAX. A simple HTML request in JavaScript to a PHP script will work too.
2) Also your <button> tag needs to be embedded in a <form> should point to a script to be executed (can be the same script).
3) You never seem to call incrementClickCount(), at least not in the part shown here.
Suggestions
The would code everything in PHP and then address the other two points. Or you need to implement some form of client / server communication.

send/update integer to flask with Javascript

I created a website using flask with a running sqlite-db (SQLAlchemy). I want to send an integer with javascript to flask and back. I know AJAX can be used to accomplish that, but I don't know how to send the integer whenever my if/else-statement within my javascript game is met.
games.html
if (loc == unicornloc) {
money = 5000;
alert("\n\nBRAVO! You found the Unicorn! :)");
}else {
money = -250;
alert("The unicorn isn't here :(")
}
<FORM method="POST" name="searchb">
<input type=checkbox onClick="javascript:search('x1y1');">
<input type=checkbox onClick="javascript:search('x1y2');">
<input type=checkbox onClick="javascript:search('x1y3');">
games.py
#app.route('/games/<money>',methods=['GET'])
#login_required
def games(money):
print(request.args.get('money', type=int))
return render_template('games.html',money)
I want to get the money-value to flask, calculate a new value, pass it to my db and show the updated value on my website without reloading the page.
first set up jquery in your html.
make sure that the jquery is included in your head section of the html page:
You won't need to submit a form to update the server it is enough if you put a listener on one of your buttons that sends an ajax request every time it is clicked:
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script>
var sendServerNotification = function(status){
var urlToCall = '/games/' + status;
$.ajax({
url : urlToCall, // the endpoint
type : "GET", // http method
// handle a successful response
success : function(parentDescriptions) {
console.log('success'); // log the returned json to the console
// update the page with jquery
},
// handle a non-successful response
error : function(xhr,errmsg,err) {
console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console
}
});
}
$( document ).ready(function() {
$('#obsevervedbutton').click(function{
//we read the value that the user wants to submit:
var valueToSubmit = $('#valueToSubmit').val()
// here you can check if you want to submit the value
// or not
if(true){
sendServerNotification(valueToSubmit);
}
});
});
</script>
</head>
<body>
<button id="obsevervedbutton">notify server</button>
<input id="valueToSubmit"></input>
</body>
and on your server side it is important to return a json response instaed of a normal http response to finish the ajax request and invoke either the success or error url:
def games(money):
print(request.args.get('money', type=int))
# update the database here
return json.dumps({"result":"operation successfull!"})
I hope this will get you going.

Categories