Update Shiny UI layout with Javascript output - javascript

I have a shinyApp running on cloud that runs for long (lots of computation taking about 25 minutes) leading to timeout. However, If I keep interacting with the page as the app runs (e.g. navigating the navbar or moving a slider..), timeout does not happen. A possible solution is to keep the web-socket active by updating a value on the current page every 5 seconds or so. I borrowed the idea here https://community.rstudio.com/t/keep-shiny-app-running-in-shiny-server-no-greying-out/27784/4.
ui <- fluidPage(
"Am I losing connection?",
tags$div(style = "position: absolute; top: -100px;",
textOutput("clock")
)
)
server <- function(input, output) {
output$clock <- renderText({
invalidateLater(5000)
Sys.time()
})
}
shinyApp(ui = ui, server = server)
However, this also fails because it appears that UI values do not update or refresh as the app runs in the background, until it completes the current task. So it appears doing this in the server.R block is not the solution.
I later tried to do it differently by including JS code that can update or refresh the UI as the app runs in the background. What I came up with is this ...
function time_function(){
var today1 = new Date();
var hr = today1.getHours();
var mn = today1.getMinutes();
var ss = today1.getSeconds();
if (hr.toString().length <2 ){hr = '0' + hr;}
if (mn.toString().length <2 ){mn = '0' + mn;}
if (ss.toString().length <2 ){ss = '0' + ss;}
console.log(hr + ':' + mn + ':' + ss);
}
setInterval(time_function,5000)
... with the view that I could update textOutput("time_display") on shiny UI every 5 seconds to keep the page active and prevent loss of connection to websocket.This is exactly where I got stuck as I cannot get textOutput("time_display") to update with refreshing values of the JS function time_function().
I appreciate all help offered. Thank you!

I managed to find a workaround that keeps the R/Shiny web page active even as intensive workloads run in server.R. I would like to share my working solution.
The first step is to create a div tag or object inside shiny's UI, fluidPage() section. I have a assigned "time_stamp" id to it.
ui<-fluidPage(
titlePanel(" Testing 5 sec display of clock"),
HTML('<div id="time_stamp"></div>'), // comment: new object created. We write to it using js code.
br(),
tags$script(src = "time_print.js")
)
//server.R
server = function(input,output,session){
// do some complex tasks..
}
The tags$script bit is where the .js file is read from. For this to happen, I saved the time_print.js file in a directory named www in the same directory as our shinyApp.R file. The code in the .js file is shown next:
function js_print_time(){
var today1 = new Date();
var hr = today1.getHours();
var mn = today1.getMinutes();
var ss = today1.getSeconds();
if (hr.toString().length <2 ){hr = '0' + hr;}
if (mn.toString().length <2 ){mn = '0' + mn;}
if (ss.toString().length <2 ){ss = '0' + ss;}
var pstamp = hr + ':' + mn + ':' + ss;
document.getElementById("time_stamp").textContent = ""; //clear current div object contents.
document.getElementById("time_stamp").textContent += pstamp; // Append pstamp to current value of div object, "". I thank Jesus!
}
setInterval(one_time_print,5000);
What this does is print the output of the javascript js_print_time() function to shiny's web page (on the "time_stamp" div) every 5000 milliseconds (5 seconds), regardless of whether heavy tasks are running in the shinyApp's server.R.

Related

Trying to email a PDF of a single Google Sheet [duplicate]

For almost a year we have been using the below code to export a Google Sheets sheet (held in theSheetName) to a PDF named as specified in InboundID.
This last week, one at a time various users can no longer produce the PDF. I get a failure at the line containing "var newFile = DriveApp.createFile(blob);" with the error being:
"Conversion from text/html to application/pdf failed."
And sure enough, the UrlFetchApp.fetch is returning HTML instead of a PDF. Again, only for some users. Does anyone have any thoughts as to why my users might be seeing this?
function sendPDFToDrive(theSheetName, InboundID)
{
var theSpreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var theSpreadsheetId = theSpreadSheet.getId();
var thisSheetId = theSpreadSheet.getSheetByName(theSheetName).getSheetId();
var url_base = theSpreadSheet.getUrl().replace(/edit$/,'');
var theOutFileName = "GASFILE_M_" + (Math.floor(Math.random() * 8997) + 1000) + '.pdf'
//export as pdf
var url_ext = 'export?exportFormat=pdf&format=pdf'
+ (thisSheetId ? ('&gid=' + thisSheetId) : ('&id=' + theSpreadsheetId))
// following parameters are optional...
+ '&size=A4' // paper size
+ '&scale=2' // 1= Normal 100% / 2= Fit to width / 3= Fit to height / 4= Fit to Page
+ '&portrait=true' // orientation, false for landscape
+ '&horizontal_alignment=CENTER' //LEFT/CENTER/RIGHT
+ '&fitw=true' // fit to width, false for actual size
+ '&sheetnames=false&printtitle=false&pagenumbers=false' //hide optional headers and footers
+ '&gridlines=true' // hide gridlines
+ '&printnotes=false' // don't show notes
+ '&fzr=true'; // repeat row headers (frozen rows) on each page
// Setup options
var options =
{
headers:
{
'Authorization': 'Bearer ' + ScriptApp.getOAuthToken(),
}
}
// Build the file
var response = UrlFetchApp.fetch(url_base + url_ext, options);
var blob = response.getBlob().setName(theOutFileName);
var folder = DriveApp.getFolderById("ABC123FooBarID");
var newFile = DriveApp.createFile(blob); //Create a new file from the blob
newFile.setName(InboundID + ".pdf"); //Set the file name of the new file
newFile.makeCopy(folder);
}
I was having this exact problem. After some debugging I saw that my URL was being created incorrectly.
My code was nearly identical to yours. Where I found the culprit was the following line:
var url_base = theSpreadSheet.getUrl().replace(/edit$/,'');
This was not actually clearing out the 'edit' to the end of the line like it had for years. I cannot say why this is, but the proof was in the logs. So, instead I crafted the url by hand:
var url = 'https://docs.google.com/spreadsheets/d/'+SpreadsheetApp.getActiveSpreadsheet().getId()+'/';
This seemed to solve the problem. This is not a perfect futureproof resolution, because if Google changes how the URLs are crafted, this will fail. But it works for now.
I hope this helps. You can send the url your code is creating to logs and check them to see if you have the same issue I did.
To expand on Jesse's accepted answer - the culprit is definitely in this line:
var url_base = theSpreadSheet.getUrl().replace(/edit$/,'');
The reason why the replace(/edit$/,'') call no longer clears out edit like before is because the URL returned by theSpreadSheet.getUrl() used to end with edit, but now returns a URL with additional parameters on the end - ouid=1111111111111111111111&urlBuilderDomain=yourdomain.com.
While rebuilding the URL entirely should also work, you can also patch the script with some small changes to the regular expression. Instead of looking for edit$ (meaning edit as the final characters in the string), you can look for edit + any additional characters like so:
var url_base = theSpreadSheet.getUrl().replace(/edit.*$/,'');
better also avoid using comma for the last property of the header pointing below:
-- 'Authorization': 'Bearer ' + ScriptApp.getOAuthToken(), --

Collection Boolean isn't being set to false - Meteor

So in short, the app that i'm developing is a bus timetable app using Meteor, as a practice project.
inside my body.js, I have an interval that runs every second, to fetch the current time and compare to items in a collection.
To show relevant times, I have added an isActive boolean, whenever the current time = sartTime of the collection, it sets it to true, and that is working fine.
But when I do the same thing for endTime and try to set it to false, so I can hide that timeslot, it just doesn't work. Even consoles don't show up. What am I missing? I have recently just started doing meteor, so excuse the redundancies.
Worth noting that the times that I'm comparing to are times imported from an CSV file, so they have to be in the 00:00 AM/PM format.
Thank you guys so much for your time.
Body.js code:
Template.Body.onCreated(function appBodyOnCreated() {
Meteor.setInterval(() => {
var h = (new Date()).getHours();
const m = ((new Date()).getMinutes() <10?'0':'') + ((new Date()).getMinutes());
var ampm = h >= 12 ? ' PM' : ' AM';
ampmReac.set(ampm);
if (h > 12) {
h -= 12;
} else if (h === 0) {
h = 12;
}
const timeAsAString = `${h}${m}`;
const timeAsAStringFormat = `${h}:${m}`;
whatTimeIsItString.set(timeAsAStringFormat + ampm); // convert to a string
const timeAsANumber = parseInt(timeAsAString); // convert to a number
whatTimeIsIt.set(timeAsANumber); // update our reactive variable
if (Timetables.findOne({TimeStart: whatTimeIsItString.get()}).TimeStart == whatTimeIsItString.get())
{
var nowTimetable = Timetables.findOne({TimeStart: whatTimeIsItString.get() });
Timetables.update({_id : nowTimetable._id },{$set:{isActive : true}});
console.log('I am inside the START statement');
}
else if (Timetables.findOne({TimeEnd: whatTimeIsItString.get()}).TimeEnd == whatTimeIsItString.get())
{
var nowTimetable = Timetables.findOne({TimeEnd: whatTimeIsItString.get() });
Timetables.update({_id : nowTimetable._id },{$set:{isActive : false}});
console.log('I am inside the END statement');
}
}, 1000); //reactivate this function every second
});
})
Very probably it is just that your if / else blocks does what you ask it:
It tries to find a document in Timetables, with specified TimeStart. If so, it makes this document as "active".
If no document is previously found, i.e. there is no timeslot which TimeStart is equal to current time, then it tries to find a document with specified TimeEnd.
But your else if block is executed only if the previous if block does not find anything.
Therefore if you have a next timeslot which starts when your current timeslot ends, your if block gets executed for that next timeslot, but the else if block is never executed to de-activate your current timeslot.
An easy solution would be to transform your else if block in an independent if block, so that it is tested whether the previous one (i.e. TimeStart) finds something or not.
Ok so I got it to work eventually. My problem was that it was never going to the second IF statement.
What I have done is set up a whole new Meteor.interval(() >) function, and placed that second IF in there, as is.
I think the problem was that it was it checks the first IF statement and gets stuck there, no matter what the outcome of the parameters is.

Appending messages to chatbox

So I am currently busy with a chatbox, which stores all the messages in Firebase and immediately appends the message into the messagebox. The problem is that Firebase obtains messages in a certain order, I append to the chatbox in a certain way, and when I refresh it, it is different again.
function init_chatbox() {
$('.empty-chat').show();
ref.child("/chatboxes/{{ chatbox.pk }}/messages/").on("child_added", function (snapshot) {
$('.empty-chat').hide();
var object = snapshot.val();
var key = snapshot.key();
var name = "";
ref.child("/users/" + object['user_id'] + "/name").once('value', function(snapshot) {
name = snapshot.val();
var timestamp = object['timestamp'];
var message = object['message'];
extra_html = ' (remove)(edit)';
$('ul.chat-messages').append('<li class="' + key +'"><p class="author"><span>' + name + '</span><span></span><span class="time" data-livestamp="' + timestamp + '"></span>' + extra_html + '</p><p class="message">' + message + '</p></li>');
});
});
When I have the code like this (with .append), it shows the code like this immediately:
Kevin 2 hours ago (remove)(edit)
some message here
John 10 minutes ago (remove)(edit)
test
John 2 minutes ago (remove)(edit)
msg
Kevin few seconds ago (remove)(edit)
test
And when i refresh the page, the message will be at the top, like the order should be.
Here is the thing, I also tried to .prepend, but it just gives another other, which isn't right either (posts the new message on top, but when refreshing it displays the good order, but then the lastest message on top).
I hope someone can help me with this, have been trying to solve this
You should take a look at orderByChild() in the firebase docs.
Basically you can order the messages by their timestamp. Like this:
ref.child("/chatboxes/{{ chatbox.pk }}/messages/")
.orderByChild("timestamp")
.on("child_added", function (snap) { ... });
Messages with the lowest timestamp will come first.

How to open an element as popup only to a certain precentage of the users

I have a js which is integrated into other websites. When the user enters the site, a function is called from the script and an element pops up.
Is it possible for me, in the js function that pops-up the element, to make it open only for a certain percentage (let's say 60%) of the users?
I thought about using the Math.random() function, but i'm not sure how to make it.
EDIT:
After thinking about it, it might be that this is not achievable by javascript alone and it will require the use of some kind of tracking of users (via database or such). If someone knows of a different way, I'll be happy to hear it.
It is easily achievable by Javascript, and it's very simple!
var pctg = 60; // Set percentage here
var rndm = Math.ceil(Math.Random() * 100);
if (rndm <= pctg) {
// Execute popup statement(s)
}
On a first glance, this code snippet gives a 60% chance that the popup will occur: that is, one page view is completely independent from another page view. It can easily happen that 20 or more subsequent visitors get the popup, but the opposite is also possible. But the more visitors, the closer the 60% will be approached.
If you want to be mercillesly accurate, you can say 'set the percentage that a given visitor will receive the popup'.
This method will not attempt any kind of balance, it relies purely on sheer number of page views. Also, do not forget, that this is per page view.
If you want to remember whether or not a certain user should receive popups between visits, you can use cookies:
document.cookie="bShowPopup=0"; or document.cookie="bShowPopup=1";
This also opens up the possibility to create a more powerful code, one that creates a given chance of displaying the popup per page view, up until the point that the popup has already been displayed once:
First a function to read and write the specified cookie string:
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1);
if (c.indexOf(name) != -1) return c.substring(name.length,c.length);
}
return "";
}
function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays*24*60*60*1000));
var expires = "expires="+d.toUTCString();
document.cookie = cname + "=" + cvalue + "; " + expires;
}
Please check out http://www.w3schools.com/js/js_cookies.asp for more information regarding the use of cookies.
Next we could do this:
// See if the user already received the popup
// The if statement will only evaluate to true if the popup is *not* explicitly disabled (that is, it is set to 1, or not set)
if (getCookie('bShowPopup') !== '0')
{
var pctg = 60; // Set percentage here
var rndm = Math.ceil(Math.Random() * 100);
if (rndm <= pctg) {
// Execute popup statement(s)
//stmt123....
// Set cookie bShowPopup to 0 (so the user won't receive popups ever again)
// NOTE: The cookie will expire after 30 days.
setCookie('bShowPopup', '0', 30);
}
}
Another approach, that will create the closest result without any server-side programming or user-tracking is to check whether a certain user should be shown popups only once, and then store the decision result in a cookie. The only thing to modify in the previous code is to set the bShowPopup cookie to 0 even if the popup wasn't shown.
if (getCookie('bShowPopup') !== '0')
{
var pctg = 60; // Set percentage here
var rndm = Math.ceil(Math.Random() * 100);
if (rndm <= pctg) {
// Execute popup statement(s)
//stmt123....
}
setCookie('bShowPopup', '0', 30);
}
Please, also refer to http://www.w3schools.com/jsref/jsref_random.asp regarding the use and return valies of Math.random().
Using Math.Random() is a good choice for this. Math.Random() generates a number between 0 and 1, so if you want a random number between 0 and 10, multiply the result by 10, then take the ceiling of that number to remove the decimal places and convert it to a round number. For 0 to 100, multiply it by 100. At the beginning of your pop-up function, add the following:
var rand = Math.Random() * 100;
next wrap the code that displays the popup in an if statement that checks to see if the random number you generated is less than or equal to 60:
if(Math.ceil(rand) <= 60) {
...
}
Heres an example of what I did when I needed this functionality
The following code would be a bit easier to maintain if you change the number of options or the chance percentage.
var contentId, random = Math.random();
if (random < 0.5) {
// option 1: chance 0.0–0.499...
contentId = 0;
} else (random < 0.75) {
// option 2: chance 0.50—0.7499...
contentId = 1;
} else {
// option 3: chance 0.75–0.99...
contentId = 2;
}
loadContent(contentId);
EDIT Based on comment feedback.
Glad to help. If this is a php page then you don't need ajax. Simply update/return the count at the top of the page via php/sql and then echo that figure into your JS function. You only need to check whether the figure is divisible by 3. If it is then run your popup function. This way you can also report on the amount of times the popup would have been activated.

How to write arguments in function?

I have been using functions but I am not able to tackle this.
What I have done is created a function, then made this to use the values provided by the document class or ids and do the work. Once work is done then just give the data back! It worked!
Now I want to make this function happen for two divs, the first function works good. The issue is with the second one. The function is correct, their is some other bug while writing the result.
Here is my code:
function time_untilCom(id) {
var Time2 = Date.parse(document.getElementById("time_" + 2).value);
var curTime2 = new Date();
var timeToWrite2 = "";
var seconds2 = Math.floor((curTime2 - Time2) / (1000));
if (seconds2 > 0 && seconds2 < 60) {// seconds..
timeToWrite2 = seconds2 + " seconds ago";
$('#update_' + 2).html(seconds2);
$('#jstime_' + 2).html(timeToWrite2 + " <b>Time that was captured!</b>");
}
}
If I use it as it is, it works! The issue comes when I try to replace these
("time_" + 2), ("#update_" + 2), ("#jstime" + 2) with ("time_" + id), ("#update_" + id), ("#jstime_" + id).
What i want to happen is that the function would be provided with a common ID that is applied throughout the div and use that ID, to get the value of time, convert it to seconds, do other stuff and then provide me with the result in the corresponding element with the id that was in the argument.
function works great, it do provide me with the result. But the issue is with the id its not being sent I guess. Or if is being sent then not being applied. What might be the issue here? And don't mind the seconds i have that covered too.
I am really very sorry for short code:
Pardon me, I was about to write the code for the function too. But electricity ran out!
Here is the code: onload="time_untilCom('2'), this is the way I am executing this.
And once in the main code, it will be executed like this: onload="time_untilCom(#row.Id) because I am using ASP.NET Web Pages I will be using the server side code to write the ID from Database. And will then user the ID throughtout the div to update the time!
From what I understand, you probably want to replace the second line
var Time2 = Date.parse(document.getElementById("time_" + 2).value);
with
var Time2 = Date.parse(document.getElementById(id).value);
And at the end you can also use
$('#'+id).html(timeToWrite2 + " <b>Time that was captured!</b>");
You are passing "id" as an argument, but you never use it inside the function. My question is: In your example you are using 2 as appendix to id attributes. Is it the 2 (or other numbers respectively) that you want to have as the id parameter of the function?
Then you could just replace each + 2 in your code by + id
function time_untilCom(id) {
var Time2 = Date.parse(document.getElementById("time_" + id).value);
var curTime2 = new Date();
var timeToWrite2 = "";
var seconds2 = Math.floor((curTime2 - Time2) / (1000));
if (seconds2 > 0 && seconds2 < 60) {// seconds..
timeToWrite2 = seconds2 + " seconds ago";
$('#update_' + id).html(seconds2);
$('#jstime_' + id).html(timeToWrite2 + " <b>Time that was captured!</b>");
}
}
EDIT: Please tell us where and how exactly do you call time_untilCom? Did you pass the id there?

Categories

Resources