I'm working on a calendar that will allow users to store events on a given date. when I update the calendar each new month I create new nodes and assign them an id so I will be able to append something to them later like so:
var i = 1;
for(var w in weeks){
var days = weeks[w].getDates();
// days contains normal JavaScript Date objects.
// alert("Week starting on "+days[0]);
var which_week = "week"+i;
i++;
for(var d in days){
console.log(days[d].toISOString());
var tr = document.getElementById(which_week);
if(days[d].getMonth()==month){
var newDay = document.createElement("div");
newDay.appendChild(document.createTextNode(days[d].getDate()));
//alert(newDay.data);
newDay.setAttribute("id", newDay.lastChild.data);
$(tr).append('<td><a class="linky" href="#">'+newDay.lastChild.data+'</a></td>');
}
else{
$(tr).append('<td class="disabledCell"><a class="linky disabledLink" href="#">'+days[d].getDate()+'</a></td>');
}
}
}
getEvents();
}
I modify the nodes:
function ajaxEventCallback(event){
var data = event.target.responseText;
data = JSON.parse(event.target.responseText);
for (var i = 0; i < data.length; i++)
//alert("event: " + data[i].title);
//
var dayOfEvent= data[i].day;
document.getElementById(dayOfEvent).appendChild(data[i].title);
}
I get a "cannot read property append child of null" error on the last line of code. I think it's a scoping issue but I don't know where to begin solving it.
At the global scope:
var allWeeks = [];
In the first function:
var i = 1;
for(var w in weeks){
var days = weeks[w].getDates();
// days contains normal JavaScript Date objects.
// week object used to store the days until it is added to the array
var week = [];
var which_week = "week"+i;
i++;
for(var d in days){
console.log(days[d].toISOString());
var tr = document.getElementById(which_week);
if(days[d].getMonth()==month){
// add the day to the temporary week object
week.push(days[d].getDate());
$(tr).append('<td><a class="linky" href="#">' + days[d].getDate() + '</a></td>');
}
else{
$(tr).append('<td class="disabledCell"><a class="linky disabledLink" href="#">'+days[d].getDate()+'</a></td>');
}
}
// add the temporary week array to the array of weeks
allWeeks.push(week);
}
In the ajax event:
function ajaxEventCallback(event){
var data = event.target.responseText;
data = JSON.parse(event.target.responseText);
for (var i = 0; i < data.length; i++) {
var dayOfEvent = data[i].day;
// Here you add the event to the appropriate day in the array
}
}
Related
I have a script that runs in a google sheet that parses emails and creates new lines in the sheet. This is used to create a log file from periodically emailed log updates. This works very well.
Currently, I have a variable that is used to determine which emails are ingested based on the month (0=January, etc.)
That variable has to be adjusted every month and then I have to create a new monthly sheet (tab in the main) and do a bunch of sorting and moving emails in gmail.
I'd like to set this up so it automatically puts the January emails in a sheet for January and the February emails in a sheet for February.
I thought about cascading if elseif statements, but that got too unwieldy fast.
I thought about iterating using a for loop through an array holding all emails, but that seems convoluted too.
Any suggestions?
::EDIT::
To be clear, I'm really interested in how to parse all of the emails and send the ones from January to the January sheet (for example).
::EDIT:: Added current script
function myFunction() {
var label = GmailApp.getUserLabelByName(myLabel);
var label2 = GmailApp.getUserLabelByName(newLabel);
var threads = label.getThreads();
var data = new Array();
var newData = new Array();
// get all the email threads matching myLabel
for (var i = 0; i < threads.length; i++) {
var messages = GmailApp.getMessagesForThread(threads[i]);
// archive thread
label2.addToThread(threads[i]);
label.removeFromThread(threads[i]);
// get each individual email from the threads
for (var j = 0; j < messages.length; j++) {
var bodyText = messages[j].getPlainBody();
// split the email body into individual "paragraph" strings based on the regExp variable
while (matches = regExp.exec(bodyText)) {
var logdata = matches[1];
for (k in keys) {
logdata = logdata.replace(keys[k], "");
}
// split out each "paragraph" string into an array
var lines = logdata.split(/[\r\n]+/);
for (l in lines) {
lines[l] = lines[l].replace('*F','');
lines[l] = lines[l].trim();
}
for (l in lines) {
lines[l] = lines[l].replace(/^(\:\s)/, "");
}
// Turn the first element in the array into a date element, format it, and put it back
lines[0] = Utilities.formatDate(new Date(lines[0]), "America/Phoenix", "M/d/yy HH:mm:ss");
// Put the array to a new item in the data array for further processing
if (curMonth == (new Date(lines[0]).getMonth())) {
data.push(lines);
}
}
}
}
// Compare the information in the data array to oldData information in the sheet
if (data.length) {
var oldData = s.getRange(range).getValues();
for (h in oldData) {
oldData[h][0] = Utilities.formatDate(new Date(oldData[h][0]), "America/Phoenix", "M/d/yy HH:mm:ss");
}
for (i in data) {
var row = data[i];
var duplicate = false;
for (j in oldData) {
if (row.join() == oldData[j].join()) {
duplicate = true;
}
}
if (!duplicate) {
newData.push(row);
}
}
// check to write newData only if there is newData, this stops an error when newData is empty
if (newData.length) {
s.getRange(s.getLastRow() + 1, 1, newData.length, newData[0].length).setValues(newData);
}
s.getRange(range).sort(1); //sorts the sheet
}
}
Try this:
function getSheet(date) {
var ss=SpreadsheetApp.openById('SpreadsheetId');
var name=Utilities.formatDate(new Date(date), Session.getScriptTimeZone(), "MMMyyyy")
var sh=ss.getSheetByName(name);
if(!sh) {
var sh=ss.insertSheet(name);
}
return sh;
}
I ended up basing my solution on the solution that #cooper provided, but had to go a little further.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var month = new Array("January","February","March","April","May","June","July","August","September","October","November","December");
var curMonth = new Date().getMonth(); //number of month -1 aka: January = 0
var sheetname = month[curMonth] + " " + new Date().getYear();
var s = ss.getSheetByName(sheetname);
function newMonth(){
if (!s) {
var template = ss.getSheetByName('Template').copyTo(ss);
template.setName(sheetname);
s = ss.getSheetByName(sheetname); //"reload" the sheet
s.showSheet(); //unhide the new copy since 'Template' is hidden in the spreadsheet
ss.setActiveSheet(s); //make it active
ss.moveActiveSheet(0); //move it to the first position
}
}
I need to move data from two sheets to one separate sheet. My code works provided the sheet is laid out as follows:
However, the sheet must be laid out like this:
I need to insert data above the row containing Amount Total but below the first row containing the headers. I feel this may be possible by adding rows above the Amount Total using the script but my current code uses getLastRow() to skip over the existing data in exports sheet. This does not work when Amount Total is placed as shown in the 2nd image. How can I achieve this? My existing code is below:
function exportData() {
var ards = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Newtownards");
var bangor = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Bangor");
var export = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Export");
var lastRow = export.getLastRow();
var nextCell = lastRow + 1;
//get values for export
var ardsRefValues = ards.getRange("B12:G12").getValues();
var ardsAmountValues = ards.getRange("B13:G13").getValues();
var bangorRefValues = bangor.getRange("B12:G12").getValues();
var bangorAmountValues = bangor.getRange("B13:G13").getValues();
for(var i = 0; i<=6; i++){
var a = ardsRefValues.join().split(',').filter(Boolean);
var b = ardsAmountValues.join().split(',').filter(Boolean);
var c = bangorRefValues.join().split(',').filter(Boolean);
var d = bangorAmountValues.join().split(',').filter(Boolean);
}//close for loop
//find length of the arrays
var aLength = a.length - 1;
var cLength = c.length - 1;
//loop through ards data arrays
for(i = 0; i<=aLength; i++){
export.getRange(nextCell, 5).setValue(a[i]);
export.getRange(nextCell, 4).setValue(b[i]);
nextCell++
}//close for loop
//loop through bangor data arrays
for(i = 0; i<=cLength; i++){
export.getRange(nextCell, 5).setValue(c[i]);
export.getRange(nextCell, 4).setValue(d[i]);
nextCell++
}//close for loop
var data = new Array();
var lastRow = export.getLastRow();
var total = 0;
for(var i = 2; i<=lastRow; i++){
var range = export.getRange(i, 4);
data = range.getValue();
total += data;
}
export.getRange(2, 7).setValue(total);
}//close function
Update: Good news is that these sheets will be updated every week. So the amount Total must be added into the row just below the last row of data
I'm trying to run an IF function to match the date in the first column to "last month" and the date in the last column to "newest date" and copy and paste all of the rows matching this criteria (excluding the first and last column) to the bottom of the list.
This is the script I'm running and it isn't finding any matches when I know for a fact there are at least 100 rows matching this criteria:
function myFunction() {
var MCS = SpreadsheetApp.openById('[ID REMOVED FOR THIS Q]');
var MRB = MCS.getSheetByName('Media Rates Back');
var MRBrange = MRB.getRange(1,1,MRB.getLastRow(),1).getValues();
var dest = MRBrange.filter(String).length + 1;
var LM = new Date();
LM.setDate(1);
LM.setMonth(LM.getMonth()-1);
var LMs = Date.parse(LM);
var Datenew = MRB.getRange(MRB.getLastRow(),MRB.getLastColumn()).getValue();
var Datecol = MRB.getRange(1,6,MRB.getLastRow(),1).getValues();
var Datenews = Date.parse(Datenew);
for(var i=0; i<MRBrange.length; i++) {
if(Date.parse(MRBrange[i])==LMs && Date.parse(Datecol[i])==Datenews ) {
var NewRange = MRB.getRange(i,2,(MRB.getLastRow()-i),5);
var NewRangeV = NewRange.getValues();
var destination = MRB.getRange(MRB.getLastRow()+1,2);
Logger.log(NewRange);
NewRange.copyTo(destination);
}else{
Logger.log(i);
}
}}
Any help would be appreciated!
Rather than get the columns as separate ranges, I would get the entire range as one array, then loop over that and check the two columns.
I'm also assuming your values are formatted as dates in the Sheet, in which case you don't need to use Date.parse(), and that your actual date logic is correct.
You can try using the debugger and set a breakpoint at the IF, so you can check the values it is comparing. or put a Logger.log call to list your comparisons.
var last_month_column = 1;
var newest_date_column = MRB.getLastColumn();
var MRBrange = MRB.getRange(1,1,MRB.getLastRow(),newest_date_column).getValues();
for(var row in MRBrange) {
if(MRBrange[row][last_month_column]==LMs && Datecol[row][newest_date_column] ==Datenews ) {
/* your copy logic here */
}else{
Logger.log(i);
}
}
I think the problem may be that MRBrange is a 2d Array. So I used another loop to convert it to a 1d array.
function myFunction() {
var MCS = SpreadsheetApp.openById('[ID REMOVED FOR THIS Q]');
var MRB = MCS.getSheetByName('Media Rates Back');
var MRBrangeA = MRB.getRange(1,1,MRB.getLastRow(),1).getValues();//2d array
var MRBrange=[];
for(var i=0;i<MRBrangeA.length;i++)
{
MRBrange.push(MRBrangA[i][0]);//1d array
}
var dest = MRBrange.filter(String).length + 1;
var LM = new Date();//current day
LM.setDate(1);//first day of month
LM.setMonth(LM.getMonth()-1);//first day of last month
var LMs = Date.parse(LM);
var Datenew = MRB.getRange(MRB.getLastRow(),MRB.getLastColumn()).getValue();
var Datecol = MRB.getRange(1,6,MRB.getLastRow(),1).getValues();
var Datenews = Date.parse(Datenew);
for(var i=0; i<MRBrange.length; i++) {
if(Date.parse(MRBrange[i])==LMs && Date.parse(Datecol[i])==Datenews ) {
var NewRange = MRB.getRange(i,2,(MRB.getLastRow()-i),5);
var NewRangeV = NewRange.getValues();
var destination = MRB.getRange(MRB.getLastRow()+1,2);
Logger.log(NewRange);
NewRange.copyTo(destination);
}else{
Logger.log(i);
}
}}
I currently have a list with two columns. The first column is student name, and the second column is the number of points they have.
I imported this list from multiple spreadsheets so there were many duplicates on the names of the students. I am able to remove the duplicates, but I want to keep a tally on the total points they have. For example:
Amy 10
Bob 9
Carol 15
Amy 12
would turn into:
Amy 22
Bob 9
Carol 15
This is what I have so far:
var target = SpreadsheetApp.getActiveSpreadsheet();
var sheet = target.getSheetByName("Sheet2");
var data = sheet.getRange("A2:B1000").getValues();
var newData = new Array();
var k = 0
var finallist = []
for(i in data){
k++;
var row = data[i];
var duplicate = false;
for(j in newData){
if(row[0] == newData[j][0]){
duplicate = true;
var storedHour = sheet.getRange("B"+k).getValue();
var position = finallist.indexOf(row[0]);
var originalCell = sheet.getRange("B"+(position+1));
var originalHour = originalCell.getValue();
originalCell.setValue(originalHour + storedHour);
sheet.getRange(k,2).setValue("")
sheet.getRange(k,1).setValue("")
}
}
if(!duplicate){
newData.push(row);
finallist.push(row[0])
}
}
}
The problem I'm having is that we have a really large data sample and I'm afraid it may run over Google's 5 minute maximum execution time. Is there another more efficient way to achieve my goal?
Your code is running slow because Spreadsheets API methods (like getRange) are time consuming and much slower then other JavaScript code.
Here is optimized function with reduced number of such Spreadsheets API calls:
function calcNumbers()
{
var target = SpreadsheetApp.getActiveSpreadsheet();
var sheet = target.getSheetByName("Sheet2");
var lastRow = sheet.getLastRow();
var dataRange = sheet.getRange(2, 1, lastRow-1, 2);
var data = dataRange.getValues();
var pointsByName = {};
for (var i = 0; i < data.length; i++)
{
var row = data[i];
var curName = row[0];
var curNumber = row[1];
// empty name
if (!curName.trim())
{
continue;
}
// if name found first time, save it to object
if (!pointsByName[curName])
{
pointsByName[curName] = Number(curNumber);
}
// if duplicate, sum numbers
else
{
pointsByName[curName] += curNumber;
}
}
// prepare data for output
var outputData = Object.keys(pointsByName).map(function(name){
return [name, pointsByName[name]];
});
// clear old data
dataRange.clearContent();
// write calculated data
var newDataRange = sheet.getRange(2, 1, outputData.length, 2);
newDataRange.setValues(outputData);
}
Sorting before comparing allows looking at the next item only instead of all items for each iteration. A spillover benefit is finallist result is alphabatized. Execution time reduction significant.
function sumDups() {
var target = SpreadsheetApp.getActiveSpreadsheet();
var sheet = target.getSheetByName("Sheet2");
var data = sheet.getRange("A2:B" + sheet.getLastRow()).getValues().sort();
var finallist = [];
for(var i = 0; i<= data.length - 1; i++){
var hours = data[i][1];
while((i < data.length - 1) && (data[i][0] == data[i+1][0])) {
hours += data[i+1][1];
i++;
};
finallist.push([data[i][0], hours]);
};
Logger.log(finallist);
}
Edit: the simple data structure with the name being in the first column allows this to work. For anything more complex understanding and applying the methods shown in #Kos's answer is preferable
Looking to extend my javascript object, I want to find the minium and maximum of a multicolumn csvfile. I have looked up solutions but I cannot really grasp the right way. I found a solution here: Min and max in multidimensional array but I do not get an output.
My code that I have for now is here:
function import(filename)
{
var f = new File(filename);
var csv = [];
var x = 0;
if (f.open) {
var str = f.readline(); //Skips first line.
while (f.position < f.eof) {
var str = f.readline();
csv.push(str);
}
f.close();
} else {
error("couldn't find the file ("+ filename +")\n");
}
for (var i=(csv.length-1); i>=0; i--) {
var str = csv.join("\n");
var a = csv[i].split(","); // convert strings to array (elements are delimited by a coma)
var date = Date.parse(a[0]);
var newdate = parseFloat(date);
var open = parseFloat(a[1]);
var high = parseFloat(a[2]);
var low = parseFloat(a[3]);
var close = parseFloat(a[4]);
var volume = parseFloat(a[5]);
var volume1000 = volume /= 1000;
var adjusted_close = parseFloat(a[6]);
outlet(0, x++, newdate,open,high,low,close,volume1000,adjusted_close); // store in the coll
}
}
Edit
What if, instead of an array of arrays, you use an array of objects? This assumes you're using underscore.
var outlet=[];
var outletkeys=['newdate','open','high','low','close','volume','volume1000','adjusted_close'];
for (var i=(csv.length-1);i>0; i--) {
var a = csv[i].split(",");
var date = Date.parse(a[0]);
var volume = parseFloat(a[5],10);
outlet.push( _.object(outletkeys, [parseFloat(date,10) , parseFloat(a[1],10) , parseFloat(a[2],10) , parseFloat(a[3],10) , parseFloat(a[4],10) , parseFloat(a[5],10) , volume /= 1000 , parseFloat(a[6],10) ]) );
}
Then the array of the column 'open' would be
_.pluck(outlet,'open');
And the minimum it
_.min(_.pluck(outlet,'open'));
Edit2
Let's forget about underscore for now. I believe you need to get the maximum value on the second column, which is what you put in your open variable.
¿Would it help if you could have that value right after the for loop? For example
var maxopen=0;
for (var i=(csv.length-1); i>=0; i--) {
var a = csv[i].split(",");
var date = Date.parse(a[0]);
var newdate = parseFloat(date);
var open = parseFloat(a[1]);
maxopen=(open>maxopen)? open : maxopen; // open overwrites the max if it greater
...
...
outlet(0, x++, newdate,open,high,low,close,volume1000,adjusted_close);
}
console.log('Maximum of open is',maxopen);