Output 2 plotly graphs from callback function in Flask with Ajax - ajax

I have a page with 2 graphs. I get my data from an API that gives new data every 15 seconds. I want my plots to be updated every minute.
I'm using Flask, plotly for the graphs and ajax for the callback function that updates the graphs every minutes.
I have a working code but my question is why my previous code didn't work ?
Here is the code I use and which WORKS :
monitoring.html
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
const delay = 60*1000; // 1 minute
// This function is called to update the API data every minute
function cb() {
$.getJSON({
url: "/cb_power_graphs", data: { 'data': 'RPM' }, success: function (result) {
Plotly.newPlot('chart_rpm', result, {});;
}
});
$.getJSON({
url: "/cb_power_graphs", data: { 'data': 'power_kw' }, success: function (result) {
Plotly.newPlot('chart_power', result, {});;
}
});
}
var intervalID = setInterval(cb, delay);
</script>
// plots
<div id="chart_rpm" class="chart"></div>
<div id="chart_power" class="chart"></div>
// This always works, initialize the graphs at the start of the page
<script>
d_rpm = {{ graphJSON['RPM'] | safe }};
d_power = {{ graphJSON['power_kw'] | safe }};
Plotly.newPlot('chart_rpm', d_rpm, {});
Plotly.newPlot('chart_power', d_power, {});
</script>
view.py
#app.route('/monitoring')
def monitoring():
return render_template('monitoring.html', graphJSON= power_graphs())
#app.route('/cb_power_graphs', methods=['POST', 'GET'])
def cb_power_graphs() :
data = request.args.get('data')
graphJSONs = power_graphs()
return graphJSONs[data]
def power_graphs() :
# Receive data for this page
data_to_plot = get_data_from_API() # pandas dataframe of the data ('time', 'RPM', 'power_kw')
graphJSONs = {}
for data in ['RPM', 'power_kw'] :
fig = px.line(data_lean_marine, x= "time", y= data)
graphJSONs[data] = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
return graphJSONs
Now here is the code that won't work. I will only show the parts I changed.
monitoring.html
function cb() {
$.getJSON({
url: "/cb_power_graphs", success: function (result) {
Plotly.newPlot('chart_rpm', result.RPM, {});;
Plotly.newPlot('chart_power', result.power_kw, {});;
}
});
}
views.py
#app.route('/cb_power_graphs', methods=['POST', 'GET'])
def cb_power_graphs() :
graphJSONs = power_graphs()
return Response(mimetype="application/json", response=json.dumps(graphJSONs), status=200)
The difference is that the first code sends the graphs individually with a response for each while the second one sends all of the graphs in the same response.
I've followed this solution which seems to have worked for the OP
Sending Plotly charts in a dictionary via jquery (getjson) in a Flask app
Why didn't my second code work ? I can work with the first one but I find the second one much more logical and easier to interpret since I'm going to have many more plots to deal with in the future
Another little question : what is the purpose of the " | safe" here ? It shows an error on my vscode when I put it ("Property assignment expected.javascript") but code will still execute
Thanks a lot

Related

vue 2 v-for not working after loading json with axios

I searched the internet for a solution but none did work so far.
I am having a Vue component where I want to load dropdown content afterwards. Since it does not work, I simplified the code such that it should only show me the elements (which are driver names).
The problem is, that the v-for seems not to work, as the elements are not created in the DOM.
Here goes the code:
<template>
<div class="list-group">
<a class="list-group-item" v-for="driver in drivers">
{{driver.name}}
</a>
</div>
</template>
<script>
export default {
name: "DriverComponent",
data: function (){
return {
drivers: [],
}
},
mounted(){
this.loadDrivers();
console.log(this.drivers);
},
methods: {
loadDrivers: function(){
axios.get('/api/drivers')
.then(
(response) => {
this.drivers = response.data.data;
console.log(this.drivers);
}
)
.catch(function(error){
console.log(error);
});
// console.log(this.drivers);
}
}
}
</script>
In my app.js:
require('./bootstrap');
window.Vue = require('vue').default;
Vue.component('driver-component', require('./components/DriverComponent.vue').default);
const drivers = new Vue({
el: '#driv',
});
And my html looks as follows:
<div id="driv">
<driver-component></driver-component>
</div>
As you can see, I added some logs, which look like this:
Image of console output
Interestingly, it should be the same array, but the first array is empty but the second has the right values in it.-> EDIT: clarified, thank you
to highlight the problem: i would expect a list like:
v-for created list
Instead, I get a blank page.
It works, if I initialize the drivers array with the data I get in json. However, since I load the data afterwards, it seems not to work
Thank you for your help!
BR
Johannes
EDIT:
I am using "axios": "^0.21" and the controller is:
public function index(){
return DriverResource::collection(Drivers::all());
}
this controller returns the array in a data field, therefore, I set the response.data.data (meaning two times data)
The backend returns:
{"data":[{"id":1,"name":"UPS"},{"id":2,"name":"Hermes"}]}
See the homepage for axios. Under the "Response Schema" section they provide what a response looks like. Specifically for data, it's
{
// `data` is the response that was provided by the server
data: {}
< ... omitted for brevity ... >
}
Please check again what your backend is returning or is supposed to return. Log the whole response you get.
The this.drivers = response.data.data; seems to be incorrect, however the this.drivers = response.data; should work.
You didn't say which version of axios you are using or show your configuration so I'm using the following for an example:
axios = ^0.21.1
vue-axios = ^3.2.4
Replaced "name" with "title" to match the response format below:
{
"title": "delectus aut autem",
< ... omitted for brevity ... >
},
--
loadDrivers: function(){
Vue.axios.get('https://jsonplaceholder.typicode.com/todos')
.then(
(response) => {
this.drivers = response.data; // <<-- works and many task titles are shown on page
// this.drivers = response.data.data; // <<-- DOES NOT WORK LIKE FOR YOU, blank page
console.log(this.drivers);
}
)
.catch(function(error){
console.log(error);
});
// console.log(this.drivers);
}

Vue js function countSubcategories() returns [object Promise]

countSubcategories() function returns [object Promise] where it should return row counts of mapped subcategories.
This code is in vue.js & Laravel, Any suggestions on this?
<div v-for="(cat,index) in cats.data" :key="cat.id">
{{ countSubcategories(cat.id) }} // Here subcategories row counts should be displayed.
</div>
<script>
export default {
data() {
return {
cats: {},
childcounts: ""
};
},
created() {
this.getCategories();
},
methods: {
countSubcategories(id) {
return axios
.get("/api/user-permission-child-count/" + `${id}`)
.then(response => {
this.childcounts = response.data;
return response.data;
});
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => (this.cats = response.data));
}
}
};
</script>
As Aron stated in the previous answer as you are calling direct from the template the information is not ready when the template is rendered.
As far as I understood you need to run getCategories first so then you can fetch the rest of your data, right?
If that's the case I have a suggestion:
Send an array of cat ids to your back-end and there you could send back the list of subcategories you need, this and this one are good resources so read.
And instead of having 2 getCategories and countSubcategories you could "merge" then like this:
fetchCategoriesAndSubcategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => {
this.cats = response.data;
let catIds = this.cats.map(cat => (cat.id));
return this.countSubcategories(catIds) // dont forget to change your REST endpoint to manage receiving an array of ids
})
.then(response => {
this.childcounts = response.data
});
}
Promises allow you to return promises within and chain .then methods
So in your created() you could just call this.fetchCategoriesAndSubcategories passing the data you need. Also you can update your template by adding a v-if so it doesn't throw an error while the promise didn't finish loading. something like this:
<div v-if="childCounts" v-for="(subcategorie, index) in childCounts" :key="subcategorie.id">
{{ subcategorie }} // Here subcategories row counts should be displayed.
</div>
Hello!
Based on the provided information, it could be 2 things. First of all, you may try replacing:
return response.data;
with:
console.log(this.childcounts)
and look in the console if you have the correct information logged. If not, it may be the way you send the information from Laravel.
PS: More information may be needed to solve this. When are you triggering the 'countSubcategories' method?
I would do all the intial login in the component itself, and not call a function in template like that. It can drastically affect the performance of the app, since the function would be called on change detection. But first, you are getting [object Promise], since that is exactly what you return, a Promise.
So as already mentioned, I would do the login in the component and then display a property in template. So I suggest the following:
methods: {
countSubcategories(id) {
return axios.get("..." + id);
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
// or use async await pattern
axios.get("...").then(response => {
this.cats = response.data;
// gather all nested requests and perform in parallel
const reqs = this.cats.map(y => this.countSubcategories(y.id));
axios.all(reqs).then(y => {
// merge data
this.cats = this.cats.map((item, i) => {
return {...item, count: y[i].data}
})
});
});
}
}
Now you can display {{cat.count}} in template.
Here's a sample SANDBOX with similar setup.
This is happen 'cause you're trying to render a information who doesn't comeback yet...
Try to change this method inside created, make it async and don't call directly your method on HTML. Them you can render your variable this.childcounts.

jQuery wait for an AJAX request to be completed before refreshing the page

I've got the following jQuery:
$("#delete_products").click(function() {
$(":checkbox:checked").each(function() {
var pid = $(this).val();
$.get('delete_product.php',{pid: pid});
});
location.reload();
});
There is a problem with this since the page doesn't wait for the AJAX request to be completed (MULTIPLE AJAX REQUESTS), and refreshes the page immediately and makes the AJAX request to not run and fail.
How can I do that the page will only refresh when it done loading?
I've been given this code:
$("#delete_products").click(function () {
var promises = [];
$(":checkbox:checked").each(function () {
var pid = $(this).val();
promises.push($.get('delete_product.php', {
pid: pid
}));
});
$.when.apply($, promises).done(function () {
location.reload();
});
return false;
});
But this solution just doesn't work.
any suggestions?
your code seems like it should work, but i would recommend to delete all products with one call by passing array of ids.
less work for the browser, less work for the server, faster results.
UPDATED ANSWER
$("#delete_products").click(function () {
var ids = [];
$(":checkbox:checked").each(function () {
ids.push($(this).val());
});
$.post('delete.php', { 'ids': ids } }).done(function() {
alert('hells yeah!');
});
return false;
});
and as for the server side:
$commaSeperatedIds = explode(',', $_POST['ids']);
mysql_query('DELETE FROM products WHERE id IN('.mysql_real_escape_string($commaSeperatedIds).')');
Use "success" parameter of "get"
EDIT: add counter of requests.
total_requests = $(":checkbox:checked").length;
total_success = 0;
...
$.get('delete_product.php',{pid: pid}, function (data, status, xx) {
...
total_success++;
if (total_success >= total_requests) {
location.reload();
}
});
...
Possible Solution #1:
The async parameter could help. Set it to false.
Since you`re using the $.get() function, this is done with:
$.ajaxSetup({
async: false
});
 
With the $.ajax() function, you'd simply set it like:
$.ajax({
url: ...,
async: false,
success: function(data) {}
});
More info on this can be found here.
 
Possible Solution #2:
Use the .success() callback hook OR .complete() if you want to refresh the page no mather if the request failed or not.
$.get('delete_product.php',{pid: pid}).success(function(response)
{
location.reload();
});
 
Happy coding!
 
Edit:
The questioner seems to prever sUP's answer. I'd like to provide an example of how to achieve the desired functionality with jQuery:
$("#delete_products").click(function()
{
var products = [];
$(":checkbox:checked").each(function()
{
var pid = $(this).val();
products.push(pid);
});
$.post('delete_products.php', {'products': products}).done(function()
{
location.reload();
});
});
 
If you prefer to use JSON for the post data, try:
$('#delete_products').click(function()
{
var products = [];
$(':checkbox:checked').each(function()
{
products.push($(this).val());
});
// Convert the products array into JSON
products = JSON.stringify(products);
$.post('delete_products.php', {'products': products}).done(function()
{
location.reload();
});
});
In PHP you need to parse the json string as follows:
<?php
// This creates an associative array from the JSON string
$delete_products = json_decode($_POST['products'], true);
// Use explode to make a comma separated string from the array
// for use in a SQL SELECT query:
$delete_products = explode(',', $delete_products);
Info about json_decode can be found in the PHP Manual.
JSON.stringify is not supported in older browsers. Include JSON-js if you need cross browser support.
I too believe that the desired result can probably be achieved best by collecting the product IDs and then sending a single Ajax call to take care of them all.
But since the OP put the interesting question forward of how to handle multiple Ajax requests and wait for them all to be finished I have looked at the when() method again and it seems to me that the original syntax is still faulty.
According to the jQuery manual when() is a method and therefore requires to be called with one or more argument(s) in parentheses. I have not worked with promises yet and I have not tested anything but I assume that something like the following might bring at least a different result:
$("#delete_products").click(function () {
var promises = [];
$(":checkbox:checked").each(function () {
var pid = $(this).val();
promises.push($.get('delete_product.php', {
pid: pid
}));
});
$.when(promises).done(function () {
location.reload();
});
return false;
});
As I said before, I still have not quite grasped the promises mechanisms/syntax yet ...
In the original version $.when does not have any meaningful context to work on, The apply() method does provide context but only after when has done its (unseccessful) work already.

jplayer+Ajax inserted content

I am using jPlayer to play audio files.
If I use the player on content, which is privided, when the page gets loaded, it works without any problems.
I also need it for HTML which is inserted by AJAX. Here it does not work. It seems, that the ready event is not triggered.
I wrote a function, which can be executed by click(). In that way, I can click it manually, when the HTML which contains the player is fully loaded. Here I have the same problem: The ready event is not triggered.
This is my function which works on non ajax inserted players fine:
$('.jp-jplayer').each(function () {
var src = $(this).attr('data-src');
var id = $(this).attr('id');
var post_id = $(this).attr('data-id');
alert('beg');
$('#' + id).jPlayer({
ready: function () {
$(this).jPlayer('setMedia', {
mp3: "/prelisten/_lofidl/change_of_heart_full_lofi.mp3",
});
alert('#' + id);
},
swfPath: "/wp-content/themes/Dark_3Chemical_DE_mit_Pagenavi/Dark_3Chemical_DE/audioplayer/js",
//////ERRRROOOOOR
solution: "flash, html",
supplied: "mp3",
wmode: "window",
cssSelectorAncestor: "#jp_container_" + post_id,
play: function () { // To avoid both jPlayers playing together.
$(this).jPlayer("pauseOthers");
},
repeat: function (event) { // Override the default jPlayer repeat event handler
if(event.jPlayer.options.loop) {
$(this).unbind(".jPlayerRepeat").unbind(".jPlayerNext");
$(this).bind($.jPlayer.event.ended + ".jPlayer.jPlayerRepeat", function () {
$(this).jPlayer("play");
debug($(this));
});
} else {
$(this).unbind(".jPlayerRepeat").unbind(".jPlayerNext");
$(this).bind($.jPlayer.event.ended + ".jPlayer.jPlayerNext", function () {
//$("#jquery_jplayer_4858").jPlayer("play", 0);
});
}
},
});
$("#jplayer_inspector").jPlayerInspector({
jPlayer: $('#' + id)
});
});
Currently I am setting the src manually to exclude any possible errors here.
How can I get this function running on AJAX inserted content?
EDIT:
This is the code, which fetches the html including the players:
$.get('/query_posts.php', {
paged: _page,
cats: cols
}, function(data) {
$('#search-results').append(data).fadeIn(300);
//create_player_scripts();
//set_players();
$('#search-results').find('input[name="cartLink"]').each(function() {
$(this).val($(this).closest('.post1').find('.post_headl a').attr('href'));
});
});
To make an AJAX page reload work I had to first destroy all jplayer instances. So I wrote a little function that grabs all instances of a jplayer on the site (by looking for jp-audio classes) and calls jplayer('destroy'); and jplayer('clearMedia'). This function gets called in the $.ajax({ beforeSend: destroyJplayerInstances(); })
UPDATE:
Here is a statement from the developer of jPlayer, Mark Panaghiston:
https://groups.google.com/forum/#!topic/jplayer/Q_aRhiyYvQo
Hope that helps!

Synchronizing with server time - jquery countdown plugin and php

I have a following code in my php file:
<div id="clockTimestamp" class="hidden"><?=$_SERVER['REQUEST_TIME'];?></div>
<div id="clock"></div>
I am using the jquery countdown plugin from Keith Wood and am calling it like this:
$('#countdown').countdown({until: until, serverSync: getServerTime});
And following code in the js file:
function getServerTime() {
var time = $('#clockTimestamp').html()*1000;
time = new Date(time);
return time;
}
Now, my question is: is this approach with the $_SERVER['REQUEST_TIME'] OK or should I do it like this:
function getServerTime() {
var time = null;
$.ajax(
{
url: '../../_ajax/getServerTime.php',
async: false,
dataType: 'text',
success: function(text) {
time = new Date(text);
},
error: function(http, message, exc) {
time = new Date();
}
});
return time;
}
I'm sure you've solved this now, but for later visitors....
The easiest way to do this would be to use a jQuery server time plugin

Resources