I previously asked a two-part question, and only one part was answered and then closed, while the other part was left unanswered. I'm re-asking that part.
I'm in charge of creating an "automatic" project tracking sheet for my team. Automatic in the sense that someone presses a button, it logs a date/time stamp, and the time they spent on said project is automatically calculated for them.
However, when I connect a script to a Google Drawing to act as a button, it doesn't do anything, and I am unable to figure out why.
Here is what my spreadsheet looks like, with a button (drawing) configured to run the timeStamp() function when clicked:
You can also get a copy of the spreadsheet here.
The code I've got so far:
function timeStamp() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var timezone = ss.getSpreadsheetTimeZone;
var timeFormat = "hh:mm a";
var lastCol = sheet.getLastColumn();
var lastRow = sheet.getLastRow();
var lastCell = sheet.getRange(lastRow, lastCol);
var timeStampCell = sheet.getRange(lastRow, lastCol + 1);
if(timeStampCell === " ") {
var date = Utilities.formatDate(new Date(), timezone, timeFormat);
timeStampCell.setValue(date);
};
}
var timeStampCell = sheet.getRange(lastRow, lastCol + 1);
...sets timeStampCell to a Range object.
if(timeStampCell === " ") {
...compares that Range object to a String containing a single space character. The comparison is always false, because the types are different. If == was used for the comparison with type coercion allowed, it would still evaluate false because the operands are just different.
You probably want the cell's contents, and to compare them to an empty string:
if(timeStampCell.getValue() === "")
Note: take care using === with the contents of Spreadsheet cells. If the comparison is for Date objects, === will evaluate true only when both operands are the exact same object. See Which equals operator (== vs ===) should be used in JavaScript comparisons?
In this example, you don't really care what data type comes from the spreadsheet, only that it is blank. Therefore, you could use ==.
if(timeStampCell.getValue() == "")
Another problem, here:
var timezone = ss.getSpreadsheetTimeZone;
This will get a reference to the Spreadsheet "getSpreadsheetTimeZone" method, but it will not tell you the timezone. To get the result of the method, you need to call it by including parentheses:
var timezone = ss.getSpreadsheetTimeZone();
With those two changes, the script works for me.
Related
I want to protect the document from other users if a date in Column B1 + 2days is greater than today.
I had no problem with protection part, but I can't get the IF statements to work for some weird reason I can't understand.
This is the script where i compare dates:
ss = SpreadsheetApp.getActiveSheet();
send_date = ss.getRange("B1").getValue();
limit = new Date(send_date.setDate(send_date.getDate()-2))
limit.setHours(0,0,0,0)
day0 = new Date();
day0.setHours(0,0,0,0)
ss.getRange("D1").setValue(day0);
ss.getRange("D2").setValue(limit);
ss.getRange("D3").setValue(limit>day0);
ss.getRange("D4").setValue(limit<=day0);
if (day0>limit) { ss.getRange("C1").setValue("can be edited");}
else if (day0<=limit) { ss.getRange("C1").setValue("cannot be edited");}
I set hours/minutes/seconds etc to 0 because I only need to compare the dates day by day.
What happens after I run the script?
For example, if B1 = '2017-10-24', D1 sets to today ('2017-10-26'), D2 sets to ('2017-10-22').
D3, with limit>day0 comparison gets value FALSE, D4 limit<=day0 gets TRUE.
So far so good, but when it comes to IF statements, it seems that these comparisons are reversed.
In this example, C1 is set to 'can be edited'.
For me, that means that first comparison return FALSE, the second returned TRUE.
When I set date to a date in future (i.e. '2017-10-30'), D1-D4 fields get the right values, but C1 is set to 'cannot be edited'.
I'll be grateful for any help.
You can not compare two objects (day0 and limit) like that.
But, you can compare a value of those two objects. For example, using getTime().
var day0Time = day0.getTime();
var limitTime = limit.getTime();
if (day0Time > limitTime) {
// do stuff
}
I have an issue with one of my API's I am trying to establish an API to call on a Customer Relationship Management tool called Streak, I am attempting to pull data via a HTTP Basic Auth GET which parses a JSON file through my script logic and then into my Google Sheets spreadsheet. The Google App Script after a few hours of development is working flawlessly (for my requirements) par one thing, my script is inserting the string 'undefined' into my google sheet cells whenever it cannot locate a value, a completely empty non existent field.
Image of Google Sheet Output issue can be seen below
Note: I have highlighted both the field property names and undefined values that are causing me grief
https://drive.google.com/file/d/0B4rXaBEqs2UzSU5fMFUxYW8zTm8/view?usp=sharing
The entire Code Snippet that is ran to extrapolate hundreds of records to my Google Sheet can be seen below. From my understanding my problem is within the statement row.push(field 1001 -> 1021), it is always attempting to push all fields to the sheet; however, not all records contain these fields as I may not have populated them within the source CRM. In response to this the script is populating "undefined" strings in the corresponding cells within my Sheet.
I am hoping to prevent this "undefined" string from being populated in the sheet, instead of this the cell should be blank, can someone please advise how I can tweak my code snippet to achieve this? Much appreciated! I have tried for nearly 2-3 hours to work around this issue but I am not experienced enough to find the answer, I have tried various IF statements and looked into defining variable types (i.e. integer, floating, char etc) none of which seem to work. I am sure a solution is simple once you know it, your assistance is greatly appreciated!
//Within this API we need to achieve collecting my data from Streak CRM us
//
// Notes on Google API
// https://developers.google.com/apps-script/guides/services/external
//
// Notes on Streak API
// https://www.streak.com/api/
//
//API Key YOUR_API_KEY Here
function myStreak() {
//Google Sheet Data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets();
var sheet = ss.getSheetByName("StreakCRM");
//Streak API Call
var consumerKey = 'YOUR_API_KEY_HERE'
var url = "https://www.streak.com/api/v1/boxes";
var headers = {
"contentType": "application/json",
"headers":{
"User-Agent": "MY_APP_NAME (App URL/your email address)",
"Authorization": "Basic " + Utilities.base64Encode(consumerKey)
},
"validateHttpsCertificates" :false
};
//Fetch Streak Data
var response = UrlFetchApp.fetch(url, headers);
var dataAll = JSON.parse(response.getContentText());
//Write Captured Streak Data to Google Sheet
var dataSet = dataAll;
var rows = [],
data;
for (i = 0; i < dataSet.length; i++) {
data = dataSet[i];
rows.push([data.name,
data.stageKey,
// data.fields['1001'], empty field
data.fields['1002'],
data.fields['1003'],
// data.fields['1004'], empty field
data.fields['1005'],
data.fields['1006'],
data.fields['1007'],
data.fields['1008'],
data.fields['1009'],
data.fields['1010'],
data.fields['1011'],
data.fields['1012'],
// data.fields['1013'], empty field
data.fields['1014'],
data.fields['1015'],
// data.fields['1016'], empty field
data.fields['1017'],
data.fields['1018'],
data.fields['1019'],
// data.fields['1020'], empty field
data.fields['1021'],
data.fields['1023']]);//your JSON entities here
}
dataRange = sheet.getRange(3, 1, rows.length, 19); // 19 Denotes total number of entites
dataRange.setValues(rows);
//Capture response code success or errors
var text = response.getResponseCode();
Logger.log(text);
Logger.log(response);
}
You've already accepted a good answer but if I could offer an alternative modified approach.
//Write Captured Streak Data to Google Sheet
var dataSet = dataAll,
rows = [],
data,
dataFields = ['name', 'stageKey', '1001' … ]; //include all the JSON fields you want to transfer including any deliberate blanks you don't exist.
for (i = 0; i < dataSet.length; i++) {
data = dataSet[i];
rows.push(dataFields.map(function (f) {
return data[f] || '';
});
}
The point here is to control the direct mapping of data fields once at the outset.
At the end of the loop then you'll see another alternative to the ternary if thrown in for good measure.
return data[f] || '';
Which is in the form
var foo = bar || 'default';
|| is the OR you know from if statements. In effect this pattern says "let foo be bar unless it is falsey in which case use 'default'.
This is the expected behaviour, since those properties are missing entirely from a given row of data, they are undefined variables. When you write an undefined variable to a sheet, it is converted into the string "undefined".
To handle this, you should do a check on each property name to make sure it exists, and if it doesn't use a desired default value instead.
You mentioned you tried various If statements. The way to check if a value is specifically undefined is to do:
if(data.fields['somefieldname'] === undefined)
However, usually you just want to know if the value evaluates to false, in which case you can simply do:
if(data.fields['somefieldname'])
Which will evaluate as False if data.fields['somefieldname'] is any value that evaluates to false, such as null, false, 0 or undefined, and true for any other value.
Finally, since if statements are a bit long for this particulay usage, I suggest using the "Conditional Operator" or "Ternary Operator" to handle this in your code:
( (data.fields['1002'])? data.fields['1002'] : '' )
Which can be read as "if data.fields['1002'] is true, return data.fields['1002'], else return empty string"
Here is some documentation on the the Conditional Operator:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator
In a snippit of your code, it would look like this:
rows.push([data.name,
data.stageKey,
// data.fields['1001'], empty field
(data.fields['1002'])?data.fields['1002']:'',
(data.fields['1003'])?data.fields['1002']:'',
...
...
]);
You can specify any value for the "else" condition, a 0 instead of an empty string, for example.
I am writing a script to bind two cells to always hold the same value after every edit. My script so far looks like this:
function myEdit(event){
var ss = event.source.getActiveSheet();
var r = event.source.getActiveRange();
var bind1 = ss.getRange("D11:D11").getCell(1,1);
var bind2 = ss.getRange("D10:D10").getCell(1,1);
var editedCell = r.getCell(1,1);
Logger.log("checking");
if (editedCell === bind1){
Logger.log("Condition was Met!");
bind1.copyTo(bind2);
}
else if (editedCell === bind2){
Logger.log("Condition was met!");
bind2.copyTo(bind1);
}
else{
Logger.log("No condition was met.");
}
When I change the value in either of the bound cells, nothing is copied anywhere else. When I check the execution logs, it appears everything went fine
[14-07-21 10:47:06:215 EDT] Execution succeeded [0.884 seconds total runtime]
But when I check the Logger logs, I get
[14-07-21 10:55:49:260 EDT] checking
[14-07-21 10:55:49:260 EDT] No condition was met.
For some reason, the program is not recognizing that editedCell and bind1 are the same object, when I edit cell D11.
By checking the values of bind1 and editedCell, the values are the same so they must be referring to the same cell, but the equality operator is not working to check whether they are the same range.
For some reason, the program is not recognizing that editedCell and bind1 are the same object, when I edit cell D11.
The reason that the identity operator === doesn't work in this case is that what is being compared is the Range, not the content of the cells in those ranges. Like Sandy suggests, you need to get the values first, and compare those.
However, comparison isn't needed, is it? Your stated objective is to ensure D10 and D11 contain the same value after every edit. Since the only edit that can affect either is a change to themselves, you can ignore any edit made to any cells but D10 or D11. Further, if either of them changes, and myEdit() is invoked, you know they are different, so you can just copy the newly changed value to the other cell.
Something like this:
function myEdit(event){
// To check whether we need to do anything, find which cell was edited.
// Get the A1 notation for the changed cell.
var cellA1 = event.range.getA1Notation();
if (cellA1 == "D10") {
// D10 changed, so copy value to D11
event.range.offset(1,0).setValue(event.value);
}
else if (CellA1 == "D11") {
// D11 changed, so copy value to D10
event.range.offset(-1,0).setValue(event.value);
}
// Any other cell, do nothing.
}
I am trying to find the best approach to comparing date/times using Javascript in order to prevent double booking on a SharePoint calendar. So I load an array with items that contain each event, including their start date/time and end date/time. I want to compare the start date/time and end date/time against the start/end date/times in the object, but I am not sure how to ensure that dates will not lapse.
like:
//date that is created from user controls
var startDate = new Date(startDat + 'T' + startHour + ':' + startMin + ':00');
var endDate = new Date(endDat+ 'T' + endHour+ ':' + endMin+ ':00');
for ( var i = 0; i < allEvents.length; i++ ) {
var thisEvent = allevents[i];
//having trouble with the compare
//i have tried silly ifs like
if (thisEvent.startDate >= startDate && thisEvent.endDate <= endDate) {
// this seems like I am going down the wrong path for sure
}
}
I then tried breaking apart the loaded object into seperate values (int) for each component of the date
var thisObj = { startMonth: returnMonth(startDate), startDay: returnDay(startDate), etc
but I am not sure this isn't just another silly approach and there is another that just makes more sense as I am just learning this.
I have a similar requirement in progress but chose to solve it at the booking stage, with jQuery/SPServices.
The code is still in build (ie not finished) but the method may help.
I attach an event handler to a column, then on selection, fetch all the dates booked in the same list to an array, then display that array on a rolling 12 month cal, as below.
I'm not checking to ensure a new booking doesn't overlap but a quick scan through the array on Pre-Save would provide a strict Go/No Go option for me. Relies on client side JS though, so not going to work in a datasheet or web services context.
I have been working for some time on an application form for an insurance company. They sell travel insurance.
As an additional small feature they have asked me to take the dates the depart and return dates the user inputs, calculate the number of days between them and then display number in the 'days' box. The goal of which is so that the user can enter the two dates and have the number of days auto-calculated.
I have already created a function which properly calculates the date and I have tested it using manually assigned date values.
My issue has come up when using the JS calendar.
What I tried to do was use the onblur of the second box to access the function and spit the date out into the 'days' box. I quickly realized that the onblur is triggered before the code for the JS calendar puts in the date, hence there is no date for the function and the function does not run.
I then tried to use onchange and realized it would not work either because the user is not actually changing the date, code is.
So what I tried to do next was use an Interval to trigger the function, this is where I have run into issues.
Below is my code in my caldate.js file which is attached to my HTML form.
var namestart = new Array ();
namestart[0] = "trav_emer_single_date_go";
namestart[1] = "trav_emer_extend_date_go";
namestart[2] = "allinc_single_date_go";
namestart[3] = "allinc_annual_date_go";
namestart[4] = "cancel_date_go";
namestart[5] = "visitor_supervisa_date_go";
namestart[6] = "visitor_student_date_go";
namestart[7] = "visitor_xpat_date_go";
var namend = new Array ();
namend[0] = "trav_emer_single_date_ba";
namend[1] = "trav_emer_extend_date_ba";
namend[2] = "allinc_single_date_ba";
namend[3] = "allinc_annual_date_ba";
namend[4] = "cancel_date_ba";
namend[5] = "visitor_supervisa_date_ba";
namend[6] = "visitor_student_date_ba";
namend[7] = "visitor_xpat_date_ba";
var names = new Array ();
names[0] = "trav_emer_single_days";
names[1] = "trav_emer_extend_days";
names[2] = "allinc_single_days";
names[3] = "allinc_annual_days";
names[4] = "cancel_days";
names[5] = "visitor_supervisa_days";
names[6] = "visitor_student_days";
names[7] = "visitor_xpat_days";
function daysBetween() {
for (var i = 0; i < 8; i++) {
//Get the value of the current form elements
var start = document.getElementById(namestart[i]).value;
var end = document.getElementById(namend[i]).value;
//Duration of a day
var d = 1000*60*60*24;
// Split Date one
var x = start.split("-");
// Split Date two
var y = end.split("-");
/// // Set Date one object
var d1 = new Date(x[0],(x[1]-1),x[2]);
// // Set Date two object
var d2 = new Date(y[0],(y[1]-1),y[2]);
//
// //Calculate difference
diff = Math.ceil((d2.getTime()-d1.getTime())/(d));
//Show difference
document.getElementById(names[i]).value = diff;
}
}
function interval() {
var int = setInterval(function(){daysBetween()},500);
}
The list of arrays at the beginning is the names of the elements which I need to access. What I intend to do with my function is on each interval run through a loop which checks all 8 of these elements. namestart[] and namend[] are the start and end dates entered by the user. names[] lists the names of the boxes where days are to be displayed.
I have not been able to even test the interval portion because I can't even get the daysBetween() to run once with manually assigned value="date" for testing purposes, it just won't run at all.
The issue is quite simple really: document.getElementById(namestart[i]).value isn't pullig out a value, it just hangs the script because it can't find the value. I have also tried using the form_name.elements().value notation and the form_name.element_name.value notation to no avail.
I am really stumped here as far as I can tell the code should be working, I give a list of the names of the items and I tell the script to access them using - what I have used, and seen many times to work getElementByID.
Any assistance would be greatly appreciated as I am not quite sure where to go from here.
As requested here is a JS fiddle link: http://jsfiddle.net/L2H9N/ - pure JS no libraries.
What I think is happening is your daysBetween function is executing before the DOM is ready, which is throwing an error and nuking the rest of your javascript. To fix it, you'll need to put the call to daysBetween into a callback for window.onload or attach it to a callback in your calender.
It would also be a good idea to coalesce your nulls or at least check for them before proceeding to do calculations on the variables.