I've got a master sheet that I need to view and filter data from. The data is brought into the sheet using the =Sheet2!B7 syntax.
The problem is that I keep accidentally editing the data in the master sheet which does not update the original copy.
I would like a script that alerts me if I'm in the master sheet and stops me from editing the cells. I still want to be able to filter the data so simply locking the sheet would work.
I've been trying to modify this script (posted by Yuzhy) but so far I haven't been able to get it to work.
(I'm not sure whether this is the right place for this question, it was either this or stackexchange / superuser, if it's wrong please advise.)
function onEdit(event){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var CellRow = SpreadsheetApp.getActiveRange().getRow();
var CellColumn = SpreadsheetApp.getActiveRange().getColumn();
if (CellColumn == 2){
sheet.getRange(CellRow, CellColumn).setFormula("=A"+CellRow+"+1");
Browser.msgBox("DON'T MESS WITH THE FORMULAS!");
}
}
Any help much appreciated.
From reading your code it seems that you're trying to do a different thing from what you've explained. I see that you trying to "repair" the formulas. But why if (CellColumn == 2 )? You want to protect only column B, not the whole "master" sheet?
Can you share a sample spreadsheet of what your sheet structure looks like?
Anyway, I'll suppose that you want to warn when you overwrite anything on your "master" sheet, auto-repairing the formula is a little more complicated (depending on your scenario), and after reading the popup it's easy to just hit undo and recover it. But you really mean to auto-repair the formulas, I'll enhance my code after you share a sample of your spreadsheet, since it's a slightly complicated and depends highly on your structure.
function onEdit() {
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "Master" )
Browser.msgBox("DON'T MESS WITH THE FORMULAS!");
}
If you're doing simple reference formulas on other sheets it's somewhat easier and probably a good solution for you to use an ArrayFormula, that will auto-replace back the value when you change any cell, except for the first cell, but then a manual undo will be so rare and obvious that it will not be an issue, I think. I did an example of array formula on the spreadsheet you linked, please take a look. On A1 I wrote:
=ArrayFormula(Sheet1!A1:F20)
Now, if you go and accidentally writes something on B2 for example, it value will automatically come back. Try and see if that's a good solution for you.
Update - as of 8/8/12 google has updated its spreadsheets and now allows you to have "protected ranges" - https://support.google.com/docs/bin/answer.py?hl=en&answer=63175
Related
Context
I have a g-sheet that acts as a sort of "master sheet" in which everything pours into from a bunch of other outside spreadsheets that are all consistently being live updated throughout the day.
Each outside spreadsheet I connect, routes to its own tab within our master spreadsheet through importrange function
All those tabs then route to one master tab using row ID #'s - so that everyone can just work from that tab.
The Problem
In this master tab where everything lands, I have a macro sorting the rows to bring the most recent rows to the top, among other things to keep the data clean. As I connect more sheets over time, I add to the number in the macro to accommodate new rows.
Macro a couple days ago started throwing "Service Spreadsheet timed out while accessing document with id..." then the id is the id # of the master tab itself.
Know there is probably a lot smoother way to have this done without using a large bandwidth macro in place, but optimizing the script to best fit the use-case is far out of my experience level. The macro I have in place is as follows:
function MasterSormat2() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('D1').activate();
var criteria = SpreadsheetApp.newFilterCriteria()
.setHiddenValues([''])
.build();
spreadsheet.getActiveSheet().getFilter().setColumnFilterCriteria(4, criteria);
criteria = SpreadsheetApp.newFilterCriteria()
.build();
spreadsheet.getActiveSheet().getFilter().setColumnFilterCriteria(4, criteria);
criteria = SpreadsheetApp.newFilterCriteria()
.setHiddenValues([''])
.build();
spreadsheet.getActiveSheet().getFilter().setColumnFilterCriteria(4, criteria);
spreadsheet.getRange('A1:AP11001').activate();
spreadsheet.getActiveRange().offset(1, 0, spreadsheet.getActiveRange().getNumRows() - 1).sort({column: 4, ascending: false});
spreadsheet.getRange('A:AM').activate();
spreadsheet.getActiveRangeList().setFontFamily('Calibri')
.setHorizontalAlignment('left');
spreadsheet.getRange('P:S').activate();
spreadsheet.getActiveRangeList().setHorizontalAlignment('right');
spreadsheet.getRange('U:U').activate();
spreadsheet.getActiveRangeList().setHorizontalAlignment('right');
spreadsheet.getRange('AA:AG').activate();
spreadsheet.getActiveRangeList().setHorizontalAlignment('right');
spreadsheet.getRange('AL:AL').activate();
spreadsheet.getActiveRangeList().setHorizontalAlignment('right')
.setNumberFormat('"$"#,##0.00');
spreadsheet.getRange('D4').activate();
};
Can anyone possibly point me in the right direction here when it comes to improving this?
Thanks for any help that you can provide here, I look forward to learning further
Tl;Dr:
If you are able to record the macro again, consider to use Go to range or the name box instead of using your mouse to move from one place to anohter in your spreadsheet as each click on a sheet and range adds a statement that activates the corresponding sheet / range. The .activate() methods usually are a lot slower that other options.
The alternative to record the macro again is to remove statements like spreadsheet.getRange(harcoded_ref).activate() and replace statements like spreadsheet.getActiveRangeList() by, i.e.,
spreadsheet.getRange(hardcoded_ref),
spreadsheet.getRangeList(array_of_refs),
etc.
For this, you require some writing-code-skills, JavaScript knowledge and Spreadsheet Service (Class SpreadsheetApp) knowledge. In order to be able achive the best performance possible in Google Apps Script you should consider to use the Advanced Sheets Service, more specifically the spreadsheet.batchUpdate method.
Go to range
Try the following keyboard shortcuts
Windows
Mac
Ctrl + Alt + .Ctrl + Alt + ,
⌘ + Option + .⌘ + Option + ,
If the above keyboard shortcuts don't work for you or you are on a different operative system, to open the Go to side panel, click in the Help menu, then on the search box type Go to, then select Go to range. This will open a "side panel", type the cell reference, then press enter or click on >.
Help menu
Side panel
Name box
Writing code skills and JavaScript knowledge
I suggest you to spend some time on learning the JavaScript basics in order to be able to understand the recorded macro and adapt it to your needs.
Let say that you learned about primitives, objects, properties, classes, methods, literals and variables and understand the very basics of method chaining as the macro recorder used it a lot.
One of the things that you might find that will help to optimize the recorded macros is by assigning objects to variables. I.E. assign Class Sheet object corresponding to the active sheet to the variable named sheet:
var sheet = spreadsheet.getActiveSheet();
Then replace all the spreadsheet.getActiveSheet() by sheet.
In order to improve the performance of your recorded macro, also you should replace
spreadsheet.getRange(something).activate();
by
var rangeSomething = spreadsheet.getRange(something);
then replace the spreadsheet.getRange(something).chain1 before the following spreadsheet.getRange(something).activate(); by rangeSomething.chain1
If you find multiple likes like
spreadsheet.getRange('P:S').activate();
spreadsheet.getActiveRangeList().setHorizontalAlignment('right');
replace these lines by something like this:
var rangeList = spreadsheet.getRangeList(['P:S','U:U','AA:AG','AL:AL']);
rangeList.setHorizontalAlignment('right');
Related
GOOGLE script 'copyTo values only' does not work when the source is a function (e.g. NOW())
My google sheet has a cell on sheet1 that contains a link to a cell on sheet2. In my function, I am able to get the link url, but cannot figure out how to get a range from the rangeId:
var link = generatorSheet.getRange(currRow, 2)
var linkUrl = link.getRichTextValue().getLinkUrl()
Logger.log(linkUrl) // linkUrl = "rangeid=1843553975"
I've tried using getRangeByName and various other functions but keep getting a null value back, not a Range object.
Thanks in advance!
Edit: My overall goal in this is to iterate over each row in sheet1, where each cell in column 2 links to a cell in sheet2. I need to take the value from the cell in sheet2 and copy it into sheet3. In sheet1, there's a check box in column 1 of each row, so that's what I'm using to determine whether or not the linked to value will be copied. I'll have a button to kick off my function and populate sheet3, and it has to assume these links are already in place - they were done by hand prior
When you create an hyperlink to a range using the user interface, you are facing this issue. I think you may have to change the way of designing the hyperlink and try to define it by the formula
=hyperlink("#gid=123456789&range=A2","go to ...")
and then you will retrieve the range by
Logger.log(linkUrl.match(/(?<=range=).*/g))
For documentation purposes,
This is a url hash fragment:
#rangeid=1843553975
The id seems to be created, when inserting a link to a range using the user interface. This is distinctly different from a namedRange When clicked, it's appended to the url in the browser, i.e.,https://docs.google.com/spreadsheets/id/edit#rangeid=1843553975. Once appended, through onpopstate javascript event, the range linked to the id is highlighted in the browser/app.
NamedRanges has a similar workflow. It also provides a rangeid=<10 digit ID>. But, it also has a name attached to it. But even in this case, the rangeid is not retrievable, though Sheets API provides a obfuscated range id.
There was a feature request made to Google, but it was made obsolete, because of lack of response on the part of the requestor:
https://issuetracker.google.com/issues/162810351
You may create a new similar issue there with a link to this answer. Once created, link the issue here.
Other related trackers:
https://issuetracker.google.com/issues/129841094
https://issuetracker.google.com/issues/134986436
Having a hard time with the below Google Scripts JS code.
Here's what it's supposed to do.
Copy and paste info from active sheet from column M to column AA
Go to sheet 2, get the last cell with data and add one row before pasting the new information. If nothing is there then paste to the top of the sheet
Upon first paste there is no active cells yet as its a fresh sheet so the info should be pasted directly at the top
This line here is giving me the trouble, I want to put in my last_row variable into getRange. Some docs say you should be able to do something like spreadsheet.getRange("A:"last_row) but that isnt working for me. Here's the current line, spreadsheet.getRange("A1").activate();
function NEW() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('M1:AA12').activate();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Sheet2'), true);
spreadsheet.getCurrentCell().getNextDataCell(SpreadsheetApp.Direction.DOWN).activate();
var last_row = spreadsheet.getCurrentCell().getRowIndex();
spreadsheet.getRange("A1").activate();
spreadsheet.getRange('\'MAIN SHEET\'!M1:AA12').copyTo(spreadsheet.getActiveRange(),
spreadsheetApp.CopyPasteType.PASTE_VALUES, false);
};
So essentially I am trying to paste data from one sheet to a second sheet. Upon paste I need the macro to find the last row with data in it from the previous paste and add one row so new data gets pasted below without any data overlapping.
Any ideas?
Regarding your 4th point:
This line here is giving me the trouble, I want to put in my last_row variable into getRange. Some docs say you should be able to do something like spreadsheet.getRange("A:"last_row) but that isn't working for me. Here's the current line, spreadsheet.getRange("A1").activate();
It's not working because the concatenation operator is missing:
spreadsheet.getRange("A:" + last_row)
Rephrasing a previous post for more clarity-
I am importing a JSON API into Sheets and the columns are not always consistently in the same place every time it reloads which is normal for JSON from what I hear. The problem is, when I append the data into another sheet to store it, every other append has the columns all mixed up (column G is now F, etc.). This makes it really hard to dedupe considering the dedupe views the columns in different places as unique.
my question is would any of the below work -
Is there a way to have my script (below) to arrange the columns into the same column A:G every time?
Is there a way to have the API import the columns into the same place every time even though it is changing from the source?
Is there a way to re-arrange/sort column headers after appending the column similar to sorting an entire column but instead just header row.
Below is the script to append to sheet 2 which works fine but showing incase option #1 above is the best choice.
function saveData() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = spreadsheet.getSheets()[0];
var sheet2 = spreadsheet.getSheets()[1];
var rows = sheet1.getRange("A1:G"+sheet1.getLastRow()).getValues(); sheet2.getRange(sheet2.getLastRow()+1,1,rows.length,rows[0].length).setValues(rows); }
I found a solution using option 3 not using script by including the below formula into the worksheet.
=transpose(sort(transpose(A1:G),1,))
I would have liked to do this all via script but this works too.
I'm using Google App Script. I have a spreadsheet with questions on it for energy auditors about buildings they visit. The auditor is asked to put their answers to the spreadsheet's questions in certain cells. Then they can use a script I wrote to generate a more formal looking Google Document report. The report is generated via these steps: Each cell the auditor inputs an answer into is a defined range. For instance, let's say Cell B10 is defined as "buildingAddress" in spreadsheet. The auditor is asked to put the building address in that cell - let's say he inputs "55 Sample Drive, Portland". When the auditor clicks to generate a Document report, the script runs these lines:
var buildingAddress = sheet.getRangeByName('buildingAddress').getValue();
copyBody.replaceText("<buildingAddress>", buildingAddress);
The place holder in my (Document file) report template is <buildingAddress>. So the code finds this in the report template and replaces it with "55 Sample Drive, Portland" - the value the auditor entered into the spreadsheet cell.
Unfortunately, there are A LOT of such cell values I need to pull from the spreadsheet and push to a placeholder in the report document. They all fit the structure of this:
var buildingAddress = sheet.getRangeByName('buildingAddress').getValue();
copyBody.replaceText("<buildingAddress>", buildingAddress);
So, I'm wondering, can I achieve the same result but use a lot less code by using an array and for loop??? Let's say the array looks like this:
var array = ["buildingAddress", "buildingOwner", "auditorName"];
How do I set up a for loop???
Thank you!!!!!
a loop will not give you gains its exactly the same except cleaner code.
debug it and see where the slow parts are (see execution transcript or log at key steps).
For example if its slow to get a range by name, and all those named ranges are contiguous, instead make a single named range for those cells. get the range (will return array) and get the values from there. this makes a single "get range" call instead of the N you have now.
from your "how to write a loop" question, seems you are just beginning programming. Id suggest a tutorial and more practicing as stackoverflow assumes you know those basics.
Thanks! If anyone's interested, here's what worked for me. I made an array of string objects. Each string was same text as a defined range in my spreadsheet. Then I used this for loop:
for(var i = 0; i < simpleCopyReplaceArray.length; i++){
var definedRangeCellName = simpleCopyReplaceArray[i];
var cellValue = ss.getRangeByName(definedRangeCellName).getValue();
var placeHolder = "<" + definedRangeCellName + ">";
if( cellValue != ""){
copyBody.replaceText(placeHolder, cellValue);
}else{
copyBody.replaceText(placeHolder, "");}
}