Timing Tool for work - javascript

I have built a tool for timing indirect workers, it consists of a start and stop button which both place a time stamp into the google sheet and then calculates the difference to record a time. It works great however when I share it with some people it does not allow them to use it saying that they do no have access to run the script. If they open script editor they can manually run it however that will no fly because I will be sending this out to approximately 50 people.
Here is the code and start and stop are two different scripts. Please let me know if I am missing something and I appreciate the help. Thanks
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
var start = new Date();
function StartScript() {
var last = ss.getLastRow();
ss.getRange(last+1,1).setValue(last+1)
var source = ss.getRange(last+1,1).getValue();
source = Number(source);
if (source <= 16) {
ss.getRange(last+1,2).setValue(start);
}
else {
ss.getRange(last+1,2).setValue("Stop Timing");
}
}
function stop() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var date = new Date();
var last1 = ss.getLastRow();
ss.getRange(last1, 3).setValue(date);
var lastrow = ss.getLastRow()
ss.getRange("D" + (lastrow)).setFormula("=C" + (lastrow) + "-B" + (lastrow));
}

Related

Google Sheets Script for loop no longer pasting until after the script has ended or cancelled

For some reason, my code that was working as of yesterday has quit working today. I can't seem to identify what I did to cause the issue and could use some more eyes on it. It was a really simple process but I am extremely new to the language, so I'm sure I'm missing something.
The issue is that the for loop used to update/paste to cell B2 on the sheet, but now it doesn't do that until after I cancel the code or it ends (using whatever the most recent value for num was). It's causing the information to not be updated and so all I get is the information associated with whatever is in cell B2 pasted all the way down through the end of the rows. It used to wait until B2 was updated before copying and pasting to the next row, which took a while but I still had more than enough time before the 5 minute limit since I'm not running a ton of data. My only thought is that I must've made a minor edit that changed this, but I can't track it down in version history. Any help would be greatly appreciated.
function CopyandPasteNewData2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('sheet1');
var lastRow = sh.getLastRow();
var num = 6;
for(var i = 6; i<=lastRow; i++)
{
num;
sh.getRange('A'+num).copyTo(sh.getRange('B2'), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false)
sh.getRange('D2:H2').copyTo(sh.getRange('B'+num+':F'+num), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false)
num++;
}
};
When you iteratively modify the values of a sheet it is always a good practice to use flush() to apply all pending Spreadsheet changes.
Please try the following small modification:
function CopyandPasteNewData2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('sheet1');
var lastRow = sh.getLastRow();
var num = 6;
for(var i = 6; i<=lastRow; i++)
{
num;
sh.getRange('A'+num).copyTo(sh.getRange('B2'), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false)
sh.getRange('D2:H2').copyTo(sh.getRange('B'+num+':F'+num), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false)
SpreadsheetApp.flush(); // <- new code
num++;
}
};

Google Apps Script Timing Out (needs simpler approach)

I'm using this script to build statistics on each of my coworkers and it includes 15 pivot tables that frequently need to have their filters updated for different time periods. I have a lot of coworkers on this sheet, so the script for 15 tables per person ends up taking way too long and times out.
I am including one table, but the code would have 15 iterations of this per person (times 20+ people).
How can I prevent it from timing out? Either through a work around or cleaning up my code to be more efficient.
function MassUpdateofFilters() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('Andrew!A1').activate();
var sourceData = spreadsheet.getRange('Batches!1:997');
var pivotTable = spreadsheet.getRange('A1').createPivotTable(sourceData);
pivotTable.setValuesDisplayOrientation(SpreadsheetApp.Dimension.ROWS);
var pivotValue = pivotTable.addPivotValue(3, SpreadsheetApp.PivotTableSummarizeFunction.SUM);
var pivotGroup = pivotTable.addRowGroup(5);
pivotGroup.showTotals(false)
.sortDescending()
.sortBy(pivotTable.getPivotValues()[0], []);
pivotGroup = pivotTable.addRowGroup(4);
pivotGroup.showTotals(false);
var criteria = SpreadsheetApp.newFilterCriteria()
.setVisibleValues(['Andrew'])
.build();
pivotTable.addFilter(5, criteria);
criteria = SpreadsheetApp.newFilterCriteria()
.setVisibleValues([ '1', '2', '3', '5', '6', '7'])
.build();
pivotTable.addFilter(16, criteria);
criteria = SpreadsheetApp.newFilterCriteria()
.setVisibleValues(['April'])
.build();
pivotTable.addFilter(15, criteria);
criteria = SpreadsheetApp.newFilterCriteria()
.setVisibleValues(['2019'])
.build();
pivotTable.addFilter(17, criteria);
};
Google has a guide to best practices here: https://developers.google.com/apps-script/guides/support/best-practices
I see that you are calling the SpreadsheetApp service seven times. This is slow. It would be better to call it once to get the data, then call it again to write the data, (if at all possible). Also, could you use the cache service to pull the data from the spreadsheet once, and manipulate copies of it over and over for each separate user?
https://developers.google.com/apps-script/reference/cache/cache
I would first recommend going through and the best practice.
That being said I did have to run a script across a few thousand sheets and developed this little bit of code to help with timeout issues. It runs a loop that times itself out, and you can set a trigger to run it ever 5 or 10 minutes. After a few hours, it will do the trick. Keep in mind that Google will eventually time you out if you do this too much, and you may have to wait a day or so till you can run it again.
function changeAll() {
var startd = new Date();
var diff = 0;
var start = Number(PropertiesService.getScriptProperties().getProperty("START"));
Logger.log(start);
for(var i = start; i < YOUR_MAX && diff < 280; i++){
//MAKE CHANGES HERE **************************************************************************
//END CHANGE AREA ****************************************************************************
//Change time and counter for next run
PropertiesService.getScriptProperties().setProperty("START", i);
var now = new Date();
var difdate = (now.getTime() - startd.getTime()) * 0.001;
diff = difdate;
Logger.log("DIFF: " + diff);
}
}

javascript taking bit longer to execute

I am trying to achieve send sms whenever google spreadsheet gets updated with mobile no. but its taking too long to execute, surely I am making any mistake but can't find out the one. Requesting your help. below is my script.
function denver() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
ss.setActiveSheet(ss.getSheetByName("Form Responses 1"));
var sheet = SpreadsheetApp.getActiveSheet();
var dataRange = sheet.getRange("A2:M1000");
var data = dataRange.getValues();
for (i in data) {
var rowData = data[i];
var contactx = rowData[5];
var textx = rowData[8];
var Decision = rowData[12];
var EMAIL_STATUS = rowData[28];
var messages_url = "https://49.50.67.32/smsapi/httpapi.jsp?
username = maruti & password = maruti123 & from = MARUTI & to = " + contactx + " & text = " +
textx + "&coding=0&flash=2";
var options = {
"method": "post",
};
options.headers = {
"Authorization": "Basic " + Utilities.base64Encode("maruti:MARUTI")
};
UrlFetchApp.fetch(messages_url, options);
}
}
You're using a for loop over a section of the spreadsheet that seems very large, A2:M1000, and seem to be performing a number of memory accesses on each interation, similar with in. Iterating over data can be very time consuming and very likely negatively affects your run time, I'd be hesitant to use a for loop over a large data set. Fetch will also increase your run time as it takes longer to retrieve the information you're asking for. If I were you I would break this into separate functions that look at more specific ranges

Display a JavaScript variable in a page loaded in innerHTML [duplicate]

This question already has answers here:
Load scripts inside innerHTML [duplicate]
(2 answers)
Closed 4 years ago.
I have an HTML page that loads within another HTML page via innerHTML. After several days of work, this script works fine and another JS file is called for the interior page, a file (named "Unified_app.js") that basically runs some date calculations. Everything is working fine and the correct dates print to the console. However, I can't figure out on the page within a page can display the console dates. Document.write does not work in this situation (I'm assuming because of the tags are not read properly?), so I need to come up with a workaround. Any ideas?
This is the innerHTML functions as I have them:
function getYearOffset(strCutoffDate, intYearOffset)
{
var datCurrentDate = new Date();
var intCurrentYear = datCurrentDate.getFullYear();
var intCurrentMonth = strCutoffDate.substr(5, 2) - 1;
var intCurrentDay = strCutoffDate.substr(8, 2);
var datCutoffDate = new Date(intCurrentYear, intCurrentMonth, intCurrentDay);
if (Number(datCurrentDate) < Number(datCutoffDate))
{
var datRequestedDate = new Date(datCurrentDate.getFullYear(), intCurrentMonth, intCurrentDay);
}
else
{
var datRequestedDate = new Date(datCurrentDate.getFullYear() + intYearOffset, intCurrentMonth, intCurrentDay);
}
return datRequestedDate.getFullYear();
}
var script = document.createElement("script");
script.src = "/resource/resmgr/scripts/Unified_app.js";
document.head.appendChild(script);
function getInclude(strIncludeContainer, strIncludeURL)
{
var strPage = '';
var intIndexOfBodyOpen = 0;
var intIndexOfBodyClose = 0;
var objXhttp;
objXhttp = new XMLHttpRequest();
objXhttp.onreadystatechange = function()
{
if (this.readyState == 4 && this.status == 200)
{
strPage = this.responseText;
intIndexOfBodyOpen = strPage.indexOf('<body>');
intIndexOfBodyClose = strPage.indexOf('</body>');
document.getElementById(strIncludeContainer).innerHTML = strPage.substring(intIndexOfBodyOpen + 6, intIndexOfBodyClose);
}
};
objXhttp.open("GET", strIncludeURL, true);
objXhttp.send();
}
I'm using:
<script>document.write(award_year1);</script>
to write the following date calls:
const date = new Date();
let offset = 0;
const threshold = new Date();
threshold.setMonth(3); //January is 0!
threshold.setDate(3);
if (Date.now() > threshold) {
offset = 1;
}
var theDate = new Date();
var award_year1 = date.getFullYear() + offset;
var award_year2 = date.getFullYear() + 1 + offset;
console.log(award_year1);
console.log(award_year2);
When loading the page-within-a-page HTML file or the interior page itself I get the correct date calculations sent to the console, but I can't seem to get them to print within the innerHTML page when loaded into the other page. Any ideas you could send me down the right path? This is probably beyond my level of understanding of JavaScript. I thought perhaps my code was not in the correct order but I've been fiddling with this and can't seem to figure out where or why.
I'm not sure if this will solve the problem but you can try it.
As you said the document.write will not be triggered cause your JS is loaded before your DOM is.
document.addEventListener("DOMContentLoaded", function(event) {
//your functions
});
Maybe this will help you out
I guess this is just not possible. I ended up replacing the innerHTML with an iframe and that seems to have worked so that I can now use script tags. Not an ideal solution but it works

Preferred Method to (Accurately) Get Time From User Input (in UiApp - GAS)

UiApp has DateBox and DateTimeFormat
for that Class. However, there is no such thing as TimePicker or TimeBox, where a user could enter a time in a well-specified manner such as through using Google Forms:
Forms has different behavior for this Widget in Chrome vs Firefox (I much prefer the Chrome behavior). Anyway, currently I am using a TextBox to get time values, where someone would enter a time value in the following manner:
12:00 or 13:50, etc. These times would be in the 24-hour clock so that I could create new Date objects based on someDate + " " + startTime, which would act as the real start time for an event on the Calendar (this is the process I currently use in several of my applications at work). This is obviously unreliable for several reasons.
Ex: If the user entered anything except a valid 24-hour representation in HH:MM:SS, Date creation would fail.
I don't want to force my boss to be overly-precautious about how he inputs times into the UI, and I also want to avoid regexing "valid" formats and having the UI do a lot of back-end work (it would be 18 regex tests total, and if any failed I'd have to handle them individually).
So, the question: is there an efficient/preferred method of getting times in UiApp, either via TextBox or some other interface?
What about something like that ? Test app here (updated with new version, see edit)
code below :
function doGet() {
var app = UiApp.createApplication().setTitle('enter time');
var frame = app.createVerticalPanel().setStyleAttributes({'border':'solid 1px #AA6','background-color':'#FFD','padding':'15px'});
var handler = app.createServerHandler('setTime').addCallbackElement(frame);
var h = app.createListBox().setId('h').setName('h').setStyleAttributes({'margin':'5px'}).addChangeHandler(handler);
for(var n=0;n<12;n++){h.addItem(Utilities.formatString('%02d', n),n)}
var m = app.createListBox().setId('m').setName('m').setStyleAttributes({'margin':'5px'}).addChangeHandler(handler);
for(var n=0;n<60;n++){m.addItem(Utilities.formatString('%02d', n),n)}
var am = app.createListBox().setId('am').setName('am').setStyleAttributes({'margin':'5px'}).addChangeHandler(handler);
am.addItem('AM').addItem('PM');
var date = app.createDateBox().setValue(new Date()).setFormat(UiApp.DateTimeFormat.DATE_LONG).setName('date').addValueChangeHandler(handler);
var label = app.createHTML('<b>StartTime *</b><br>When your reservation starts').setStyleAttributes({'fontSize':'10pt','font-family':"Arial, sans-serif",'padding-bottom':'10px'});
var subFrame = app.createHorizontalPanel().setStyleAttributes({'border':'solid 1px #AA6','background-color':'#FFD','padding':'5px'});
var result = app.createHTML().setId('date').setStyleAttributes({'fontSize':'10pt','font-family':"Arial, sans-serif",'color':'#AA6','padding-top':'20px'})
.setHTML(Utilities.formatDate(new Date(new Date().setHours(0,0,0,0)), Session.getTimeZone(), 'MMM-dd-yyyy HH:mm'));
frame.add(date).add(label).add(subFrame).add(result);
subFrame.add(h).add(m).add(am);
return app.add(frame);
}
function setTime(e){
var app = UiApp.getActiveApplication();
var date = app.getElementById('date')
var date = new Date(e.parameter.date);
var am = e.parameter.am
if(am=='AM'){am=0}else{am=12};
var h = Number(e.parameter.h)+am;
var m = Number(e.parameter.m);
date.setHours(h,m,0,0)
Logger.log(date);
app.getElementById('date').setHTML(Utilities.formatDate(date, Session.getTimeZone(), 'MMM-dd-yyyy HH:mm'));
return app
}
EDIT : here is the wrapped version and a demo with a grid and 10 panels.
function doGet() {
var app = UiApp.createApplication().setTitle('enter time');
var grid = app.createGrid(10,2)
var handler = app.createServerHandler('setTime').addCallbackElement(grid);
var varName = 'date';
var htmlString = '<b>StartTime *</b> When your reservation starts'
for(var idx=0 ; idx<10;idx++){
var frame = pickDate(idx,varName,htmlString,handler);
grid.setText(idx, 0, 'test widget '+idx+' in a grid').setWidget(idx,1,frame);
}
var result = app.createHTML('<h1>Click any widget</h1>').setId('result');
return app.add(grid).add(result);
}
/* wrapped version
** takes a var name + index + label string + handler
** as input parameter
** The same handler will be used for every occurrence , the source being identified in the handler function (see code example below)
** and returns a selfcontained widget that you can add to a panel or assign to a grid
** or a flex Table
*/
function pickDate(idx,varName,htmlString,handler){
var app = UiApp.getActiveApplication();
var frame = app.createVerticalPanel().setStyleAttributes({'border':'solid 1px #AA6','background-color':'#FFD','padding':'1px', 'border-radius':'5px'});
var h = app.createListBox().setId('h'+idx).setName('h'+idx).setStyleAttributes({'margin':'5px'}).addChangeHandler(handler);
for(var n=0;n<12;n++){h.addItem(Utilities.formatString('%02d', n),n)}
var m = app.createListBox().setId('m'+idx).setName('m'+idx).setStyleAttributes({'margin':'5px'}).addChangeHandler(handler);
for(var n=0;n<60;n++){m.addItem(Utilities.formatString('%02d', n),n)}
var am = app.createListBox().setId('am'+idx).setName('am'+idx).setStyleAttributes({'margin':'5px'}).addChangeHandler(handler);
am.addItem('AM').addItem('PM');
var date = app.createDateBox().setValue(new Date()).setFormat(UiApp.DateTimeFormat.DATE_LONG).setId(varName+idx).setName(varName+idx).addValueChangeHandler(handler);
var label = app.createHTML(htmlString).setStyleAttributes({'fontSize':'10pt','font-family':"Arial, sans-serif",'padding-bottom':'3px'}).setId('html'+idx);
var subFrame = app.createHorizontalPanel().setStyleAttributes({'border':'solid 1px #AA6','background-color':'#FFE','padding':'1px', 'border-radius':'4px'});
frame.add(label).add(date).add(subFrame);
subFrame.add(h).add(m).add(am);
return frame;
}
function setTime(e){
// Logger.log(JSON.stringify(e));
var app = UiApp.getActiveApplication();
var idx = Number(e.parameter.source.replace(/\D+/,''));
Logger.log('date'+idx+ ' > '+e.parameter['date'+idx]);
var date = new Date(e.parameter['date'+idx]);
var am = e.parameter['am'+idx];
if(am=='AM'){am=0}else{am=12};
var h = Number(e.parameter['h'+idx])+am;
var m = Number(e.parameter['m'+idx]);
date.setHours(h,m,0,0)
app.getElementById('result').setHTML('<h1>Widget Nr '+idx+' has value '+Utilities.formatDate(date, Session.getTimeZone(), 'MMM-dd-yyyy HH:mm')+'</h1>');
return app
}

Categories