OK, still rookie coder but getting better. Able to modify codes but not write most of them myself yet. Here is my current problem.
I run a small business and we use google sheets as our CRM (its faster and easier for us to do it this way) I have a master sheet that I bring in with =importrange everyone else jobs. It works perfect, makes my life real easy but there is one thing I can not get to come over. That is the notes stored in a cell. I have to actually open their sheet to view the notes. So I am trying to get a script that would update the notes down the importedrange. Then each day when I go over their info I push a button and it would write the notes from the other persons sheet onto my sheet, can just write over the last note and replace it.
I made an example with 3 sheets (all made editable for everyone) since I can't post our actual business sheets to work with. I should be able to modify it and transfer to my actual sheets after some help.
(Master Sheet) https://docs.google.com/spreadsheets/d/1TMNyohd5Vtn3p9cpLebmZASt2TzbWL80fIesCu89-ig/edit?usp=sharing
(Employee 1)
https://docs.google.com/spreadsheets/d/1n4iFXGuC7yG1XC-UIbuT9VrQ7rJWngPkDCv0vsvDed4/edit?usp=sharing
(Employee 2)
https://docs.google.com/spreadsheets/d/1EJVa5TgF6UkLhiLtfQ6o7BzpdzXDGcVhkibLCYwlfAU/edit?usp=sharing
Links are provided to each other sheet on the top of the master sheet. This is above my head so I won't try to butcher the code below lol. Here is a function I found but don't know how to implement it to use with import range.
function getNotes(rangeAddress) {
// returns notes inserted with Insert > Note from one cell or a range of cells
// usage:
// =getNotes("A1"; GoogleClock())
// =getNotes("A1:B5"; GoogleClock())
// see /docs/forum/AAAABuH1jm0xgeLRpFPzqc/discussion
var ss = SpreadsheetApp.getActiveSpreadsheet();
var range = ss.getRangeByName(rangeAddress);
if (!range) return '#N/A: invalid range. Usage: =getNotes("A1:B5"; GoogleClock())';
var notesArray = new Array();
for (var i = 0; i < range.getHeight(); i++) {
notesArray[i] = new Array();
for (var j = 0; j < range.getWidth(); j++) {
notesArray[i][j] = range.getCell(i + 1, j + 1).getComment();
}
}
return notesArray;
}
So The code I would want it to read the note from "Employee 1" sheet and write it onto the "Master" sheet in the correct cell. Since it is an =importrange the orientation of the cells will always be the same on both sheets, just needs to pick the starting cell and go down the list. I want to make it work with the button I put on the top of the master sheet on each tab.
This script uses outdated methods. For example, GoogleClock() is gone.
Please see my other response to your similar question for a possible solution.
Related
I'm trying to send myself either an email or copy the row to a new sheet when it's someone's birthday or hire date anniversary. Copying the line to a new sheet would allow me to use zapier to notify me of the update. Either would work. The sheet uses a form to collect data.
I've built a few scripts but nothing that had to do with dates. I'm just struggling with this one and have tried a few examples I could find with no luck.
Here is this sheet. It's view only so just let me know if you need more access.
I understand that you want to replicate your form responses Sheet in another Sheet (let's call it Zapier Sheet) automatically each time that a new form response is added. You can achieve that goal developing an Apps Script code that runs at each form response. In that case you can use a code similar to this one:
function so62400514() {
var formSheet = SpreadsheetApp.openById(
'{FORM SHEET ID}').getSheets()[0];
var zapierSheet = SpreadsheetApp.openById(
'{ZAPIER SHEET ID}').getSheets()[0];
var formData = formSheet.getRange(1, 1, formSheet.getLastRow(), formSheet
.getLastColumn()).getValues();
var zapierData = zapierSheet.getRange(1, 1, zapierSheet.getLastRow(),
formSheet.getLastColumn()).getValues();
var recorded = false;
for (var fr = 0; fr < formData.length; fr++) {
for (var zr = 0; zr < zapierData.length; zr++) {
if (formData[fr].toLocaleString() == zapierData[zr].toLocaleString()) {
recorded = true;
}
}
if (recorded == false) {
zapierSheet.appendRow(formData[fr]);
} else {
recorded = false;
}
}
}
This code will first open both sheets (using SpreadsheetApp.openById() and Spreadsheet.getSheets()) to select the data with Sheet.getRange (setting boundaries with Sheet.getLastRow() and Sheet.getLastColumn()) and reading it using Range.getValues(). After that operation the data will get iterated using the property Array.length as the perimeter. The iteration compares each row from the form Sheet to every row of the zapier sheet (to accomplish that, I first parsed the row as a string with Date.toLocaleString()). If the form row is found in the zapier sheet, the boolean recorded will flag to true. After every row on the zapier sheet gets compared to the form row, the code will write it down on the zapier sheet based on the boolean flag.
As explained in the previous paragraph, this code will take the form sheet rows not present in the zapier sheet; and paste them on the zapier sheet. I used this approach to prevent missing any row (as it could happen when simultaneous users answer the form all at once). To make this fire automatically you'll need to set up an installable trigger with these settings:
As an example, let's say that we have these form responses:
And our initial sample zapier sheet looks like this one below. Please, notice how several past rows are missing;
After running the script (as it will do automatically) this would be the result:
I suggest running the script manually for an initial setup. If the timestamps diverge, please check if both spreadsheets share time zones. Don't hesitate to ask me further questions to clarify my answer.
I have a script that allows me to import data from a multitude of "child" spreadsheets to a "parent" spreadsheet.
Each child is composed of many tabs, but all the data that I import is unified thanks to queries in a single sheet of the child ("datasheet").
Problem is: if in the future I want to modify something (for example import more data from the childs), i would then have to go and edit manually every single child datasheet.
I'd rather have a script that would do the following:
In my "Parent", i would create a "datasheet" which would be the template datasheet.
The script would copy the "content" (and the formulas) of the parent datasheet.
It would paste this content+formulas in all the spreadsheets specified (in my Parent, i already have a list of URLs - one for each child).
I don't want to "copy" the parent datasheet, i'd rather replace content.
Is this even possible?
Thank you in advance for your help!
Based on our conversation on comments, I comprehend that you want to copy all data from the Parent sheet into the specified sheets. I'll assume that the list of sheets is located in the upper left corner of the Parent sheet. If that isn't the case, please forgive me and indicate to me where the sheet list is located. This is the code that fulfills your request:
function sheetPopulator(spreadsheetID) {
var masterSheet = SpreadsheetApp.openById(spreadsheetID).getSheetByName(
"Parent");
var specifiedSheets = masterSheet.getRange(2, 1, masterSheet.getLastRow(), 1)
.getValues();
var lastRowMasterSheet = masterSheet.getLastRow();
var lastColumnMasterSheet = masterSheet.getLastColumn();
var allData = masterSheet.getRange(1, 1, masterSheet.getLastRow(), masterSheet
.getLastColumn()).getValues();
for (var i = 0; i < specifiedSheets.length; i++) {
if (SpreadsheetApp.openById(spreadsheetID).getSheetByName(specifiedSheets[
i]) != null) {
SpreadsheetApp.openById(spreadsheetID).getSheetByName(specifiedSheets[i])
.getRange(1, 1, lastRowMasterSheet, lastColumnMasterSheet).setValues(
allData);
}
}
}
That function will first gather the list of specified sheets and after that will read all the data from the Parent sheet. After that, the code will iterate over every element of the list and, if that element coincides with a sheet name, it will copy all the previously read data into that sheet.
Please, take this as one of many possible solutions to your issue. Don't hesitate to ask me for clarifications or further help.
UPDATE
I apologize if the previous script doesn't fulfill your requests. After reading your new comments I studied your spreadsheets and designed a new code. I have tested this new script on copies of your spreadsheets and it works flawlessly. This is the code in question:
function sheetPopulatorII() {
var parentSheetID = "{PARENT SHEET ID}";
var childrenListSheet = SpreadsheetApp.openById(parentSheetID).getSheetByName(
"Childs");
var childrenList = childrenListSheet.getRange(2, 1, childrenListSheet
.getLastRow() - 1, 1).getValues();
for (var i = 0; i < childrenList.length; i++) {
childrenList[i][0] = childrenList[i][0].toString().substring(39, 83);
}
for (var i = 0; i < childrenList.length; i++) {
var children = SpreadsheetApp.openById(childrenList[i][0]);
children.getSheets()[0].setName("OLD SHEET");
SpreadsheetApp.openById(parentSheetID).getSheetByName("Master").copyTo(
children).setName("Data");
children.deleteSheet(children.getSheetByName("OLD SHEET"));
}
}
I am going to explain this code step by step. First, it opens the children list sheet (inside the Parent Sheet) using .openById() for opening the Parent spreadsheet, .getSheetByName() to open the involved sheet, .getRange() for selecting the list, .getLastRow() to know how long the list is (so you can add new URLs on the future using the same code) and .getValues() to gather the data.
Afterwards the code will iterate the list to convert the URLs into IDs with .substring() to cut away the non-ID parts. Next, it will iterate the list again but this time it will rename the old sheet with a temporal name using .setName(), copy the Parent sheet with .copyTo() and delete the renamed sheet with .deleteSheet(). This approach copy both: values and formulas. Please, contact me again if you need further help developing the code or understanding it.
I frequently deal with large spreadsheets and am looking for a way to easily split these into rows of 200.
To explain more clearly: I have a spreadsheet containing one sheet (or tab?) with 2000 rows.
Currently, I would open a new sheet (in the same work book), mark the first 200 rows and copy and paste them into a new sheet. Then I mark the next 200 rows and copy and paste them into a new sheet etc.
Is there a way of automating this process or speeding it up with a function?
Thanks for your time and apologies for my poor explanation.
this script should do the trick, just replace the numberOfLines
function myFunction() {
var numberOfLines = 10;
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var yourNewSheet = {};
for (var i = 0; i < data.length; i++) {
var tabName = i%numberOfLines;
if(tabName == 0){
yourNewSheet = sheet.getParent().insertSheet("tab #" + i);
}
yourNewSheet.appendRow(data[i]);
}
}
You could use an ImportRange formula, like Chris Hick suggested here.
Or you may try using scripts. See more info here:
https://developers.google.com/apps-script/reference/spreadsheet/range
If you have no luck with this, please tell what you've tried so far so we could suggest any improvements.
Create a new sheet.
If your sheet has headers, click on cell A1 of your new sheet and enter =ARRAYFORMULA( -- click on your original sheet and click on the header row. The formula should complete to =ARRAYFORMULA(NameOfSheet!1:1)
In Cell A2 of your new sheet, enter =ARRAYFORMULA( -- click on your original sheet and select row 2. The formula should auto complete to =ARRAYFORMULA(NameOfSheet!2:2). You will want to modify the second number in the function to encompass as many rows as you wish to copy to the new sheet. For example, to copy 200 rows, you would use the formula =ARRAYFORMULA(NameOfSheet!2:202)
You can continue to create additional sheets -- simply duplicate the second sheet and augment the first and second numbers to copy additional sections of your sheet, for example:
=ARRAYFORMULA(NameOfSheet!203:402)
=ARRAYFORMULA(NameOfSheet!403:602), etc.
I want to run a Google Script which normalises a cell range, on a number (30+) of Google Sheets. Or more precisely, I want a (less technical) user to be able to do this. I can't seem to find a reasonable workflow.
The options I can see are:
Copy/paste the script as a bound script to each of the spreadsheets
That's messy because then there are many copies of the script, which generally won't be run again, and there's a lot of overhead and clicking around to install the macro for each one.
Use a library
I could put the body of the code in a library, then make the copy/paste just a stub (like the accepted answer here ).
However, that's still just as bad for the UX, plus various reports that libraries are messy to deal with.
Make an add-on
The "right way" seems to be to create an add-on which the user can enable for each spreadsheet. However, add-ons still seem to be in "developer preview" mode, and the authorisation cycle is uncertain and potentially slow. Google also expects that The script has been tested with multiple active users. which would be hard - by the time I'd tested it this thoroughly, the job would basically be done. And how would I test it without publishing the add-on anyway?
Other options?
Is there some other way, perhaps using an unbound-script? It's not possible to run a single script once and have it iterate over all the spreadsheets as a bit of user input is required (which range within the spreadsheet etc).
Is there a way where the user could install an unbound script, run it, and it would ask which spreadsheet to run it on?
openByUrl() is really close, but it doesn't actually open the spreadsheet UI, so I wouldn't be able to use functions like getActiveRange() etc.
In case it's relevant, here's the script:
/*function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Normalize')
.addItem('Normalize Crosstab', 'normalizeCrossTab')
.addToUi();
}*/
function onOpen() {
var ss = SpreadsheetApp.getActive();
var items = [
{name: 'Normalize Crosstab', functionName: 'normalizeCrosstab'},
];
ss.addMenu('Normalize', items);
}
/* Converts crosstab format to normalized form. Given columns abcDE, the user puts the cursor somewhere in column D.
The result is a new sheet, NormalizedResult, like this:
a b c Field Value
a1 b1 c1 D D1
a1 b1 c1 E E1
a2 b2 c2 D D2
a2 b2 c2 E E2
...
*/
function normalizeCrosstab() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var firstDataCol = SpreadsheetApp.getActiveRange().getColumn();
var dataCols = values[0].slice(firstDataCol-1);
if (Browser.msgBox("This will create a new sheet, NormalizedResult. Place your cursor is in the first data column.\\n\\n" +
"These will be your data columns: " + dataCols,Browser.Buttons.OK_CANCEL) == "cancel") {
return;
}
var resultssheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("NormalizedResult");
if (resultssheet != null) {
SpreadsheetApp.getActive().deleteSheet(resultssheet);
}
var header = values[0].slice(0, firstDataCol - 1);
var newRows = [];
header.push("Field");
header.push("Value");
newRows.push(header);
for (var i = 1; i <= numRows - 1; i++) {
var row = values[i];
for (var datacol = 0; datacol < dataCols.length; datacol ++) {
newRow = row.slice(0, firstDataCol - 1); // copy repeating portion of each row
newRow.push(values[0][firstDataCol - 1 + datacol]); // field name
newRow.push(values[i][firstDataCol - 1 + datacol]); // field value
newRows.push(newRow);
}
}
var newSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("NormalizedResult");
var r = newSheet.getRange(1,1,newRows.length, header.length);
r.setValues(newRows);
};
The first question is: "Who is the owner of all these sheets?" If you are the owner of all these sheets, then you have permission to access them remotely. If you don't own them, then the owner would need to share, and give editing permissions to whatever code is trying to modify their file.
If you own all the spreadsheets, you could create a Stand Alone App do all the processing from a central point. Then you can just email the link of the Stand Alone App to everyone, or have each user enter a link in their spreadsheet to the Stand Alone App. As you mentioned, for that option you won't be able to use methods like, getActiveSheet().
No matter what option you use, you'll need to either have people add something to their spreadsheet, or create some new, centralized interface. The best option for you may come down to ownership and setting permissions.
I'm guessing that if the users of the spreadsheets are the owners, and don't want to give you permission, they'll need to use one of your first three options. And I'd start with the library first.
If you can easily get the file ID's of the spreadsheets, you could create an object that matches the user to the FileID.
var objUserToFileID = {"user1":"abc34ciu89384u", "user2":"FileID_Two", "user3":"FileID_Three"};
Then have a way for the user to choose their name from the list, (Drop Down List) then run the code. That's for the Stand Alone App. Of course, then you'd need to figure out what happens if the user chooses the fileID for someone else's spreadsheet. Then you'd need to have a way to determine who the user of the App is.
You can retrieve the sheets that the user provided the URLs, exhibit them in simple HTML, one sheet below the other, and append a button column, which would call normalizeCrosstab() for that ROW. This is a publishedHTML solution, anybody could use without login.
If there's a defined number of sheets you could also generate them in HTML with a button next to the name, and it would generate the TABLE HTML.
Or use the library, I doubt there's anything you need and couldn't do, that answer is pretty old (12').
I'm using librarys and having no trouble with them, really handy for everything, all sheets must have these 3 functions to work as if the script was in the sheet themself:
function onOpen() {
library.onInitialize();
}
function onEdit(celEd) {
library.onMakeEdit(celEd);
}
function libraryFuncs( funcName, args ){ // Needed for sideBars to use library functions
if(args)
args = args.split("\!|"); // Predefined separation of args
else
args = [];
return library[ funcao ]().apply(this, args);
}
I have a Google Drive Spreadsheet in which I'm trying to have a formula autopopulate all the way down the rows, as users submit data via a webform attached to the spreadsheet. The tricky part is that I use a CONCATENATE function already, to perform a concatenation of the data submitted on several columns into one single cell.
However, for the CONCATENATE function to work, I have to "apply" it to newly submitted rows.
Is there a way to automate the filling of this formula down the rows in the spreadsheet?
I've tried to place an ArrayFormula function to it, even setting the range (A1:A), but I couldn't find the proper syntax for it work, if it may like this.
The function goes:
=CONCATENATE(CHAR(10)&X14&V14&Y14&J14&" "&K14&" "&L14&M14&N14&O14&" "&P14&" "&Q14&R14&S14&CHAR(10)&T14&"."&CHAR(10)&U14&"."&CHAR(10)&W14&CHAR(10)&CHAR(10)&CHAR(10)&I14&CHAR(10)&Z14&" "&AA14&CHAR(10)&AB14&AC14&AD14&AE14&AF14&AG14&AH14&"."&CHAR(10)&AI14&AJ14)
Any suggestion will be greatly appreciated.
(Answered by the OP in a question edit. Transformed into a community wiki answer. See Question with no answers, but issue solved in the comments (or extended in chat) )
The OP wrote:
I've found a little script from the Script Gallery of Google Spreadsheet (AutoFormulas, by tuxincarnate[#]gmail[dot]com), which solves the trick! Just tested it with a dozen of submissions from the webform and it does what it promises, autopopulate with formulas down the columns where applied.
// Updates all rows except the last to have the same formulas as row 3 (allowing for a header and different row 2 formula)
// To activate this functionality, the last row in the column should have the value '(auto)'
function UpdateFormulas() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetCount = ss.getNumSheets();
for (var sheetIndex = 0; sheetIndex < sheetCount; ++sheetIndex) {
var sheet = ss.getSheets()[sheetIndex];
var rowCount = sheet.getMaxRows();
var columnCount = sheet.getMaxColumns();
if (rowCount < 5) continue;
for (var columnIndex = 1; columnIndex <= columnCount; ++columnIndex) {
if (sheet.getRange(rowCount, columnIndex).getValue() == '(auto)') {
var row3Range = sheet.getRange(3, columnIndex);
for (var rowIndex = 4; rowIndex < rowCount; ++rowIndex) {
if (sheet.getRange(rowIndex, columnIndex).isBlank()) {
row3Range.copyTo(sheet.getRange(rowIndex, columnIndex));
}
}
}
}
}
}
#David Tew wrote:
Whilst you have a solution already, you might like to consider for the future the following formula, which is what you were originally looking for (You don't need to use CONCATENATE since you are choosing the columns to join together with & symbol)
=arrayformula(CHAR(10)&X2:X&V2:V&Y2:Y&J2:J&" "&K2:K&" "&L2:L&M2:M&N2:N&O2:O&" "&P2:P&" "&Q2:Q&R2:R&S2:S&CHAR(10)&T2:T&"."&CHAR(10)&U2:U&"."&CHAR(10)&W2:W&CHAR(10)&CHAR(10)&CHAR(10)&I2:I&CHAR(10)&Z2:Z&" "&AA2:AA&CHAR(10)&AB2:AB&AC2:AC&AD2:AD&AE2:AE&AF2:AF&AG2:AG&AH2:AH&"."&CHAR(10)&AI2:AI&AJ2:AJ)
this should go in the second row
The OP wrote:
#DavidTew showed me a clear and easy way to have it solved. The ArrayFormula should go alone, telling it to put together the ranges for every column into every single row. Works like charm. Proper syntax is: =arrayformula(A2:A&B2:B&C2:C), in order to have contents from cells A2, B2 and C2 "concatenated" into the cell where the ArrayFormula is applied. The most important issue is that by using this function, it autpopulates all the way down the rows as users submit data via a webform attached to the spreadsheet.