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.
Related
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
I have two sheets, both have a column that use data validation in Column G/#7, starting from row #8 and all the way down. I have to edit the data validation every once in a while and match the lists for both sheets. This can be annoying since it starts from row 8 down to row 1000+ and needs to be done for both sheets.
How do you make it so that you have a third sheet called "settings" where there will be a master list where in one column there will be a list of rows and for each row it takes the data and automatically updates the data validation list for both sheet one and sheet two?
e.g.
1 | Master List (Title)
2 | John Doe
3 | Jane Doe
4 | Steve Smith
5 | Stacy Smith
and it makes the data validation for column 7, row 8+ (all the way down) for BOTH Sheet 1 and Sheet 2 as such:
John Doe,Jane Doe,Steve Smith,Stacy Smith
And if a name is added, it adds it to the data validation list / updates the list. If a name is removed, it removes it from the data validation list.
-- Photo examples provided:
We have a column that uses data validation to list out items.
We also have a "master list" with all those items. If I update that "master list" I want the data validation to be updated so I don't have to go into the settings for data validation but only update my list since it is always changing so I can have an updated dropdown for that column.
I believe your goal as follows.
You want to update the datavalidation rules at the column "D" (the range is "D2:D") on the sheet of "Members", when "Master" sheet is updated.
You want to achieve this using Google Apps Script.
In this case, I would like to propose to run the script using OnEdit trigger.
Sample script:
Before you use this script, please set the sheet names of "Master" and "Members" sheets. From your question, I couldn't understand about the correct sheet names. When you want to run the script, please update the cells of "Master" sheet. By this, the datavalidation rules at the cells "D2:D" on "Members" sheet are updated.
function onEdit(e) {
const masterlistSheetName = "Master"; // Please set the sheetname.
const membersSheetName = "Members"; // Please set the sheetname.
const ss = e.source;
const master = ss.getActiveSheet();
if (master.getSheetName() != masterlistSheetName) return;
const range = master.getRange("A2:A" + master.getLastRow());
const members = ss.getSheetByName(membersSheetName);
const dataValidation = members.getRange("D2").getDataValidation().copy().requireValueInRange(range).build();
const length = members.getRange("D2:D").getDataValidations().filter(String).length;
members.getRange("D2:D" + (length + 1)).setDataValidation(dataValidation);
}
Note:
This sample script is run by the OnEdit trigger. So when you directly run the function onEdit at the script editor, an error occurs because the event object is not used. Please be careful this.
This sample script supposes that your values for datavalidation rules are put to the cells "A2:A" in "Master" sheet. When you want to change this, please modify the above script.
This sample script supposes that your datavalidation rules are put to the cells "D2:D" without the empty rows. When you want to change this, please modify the above script.
References:
Class DataValidationBuilder
getDataValidation()
setDataValidation(rule)
I'm trying to convert order form data submitted from a Squarespace website from the following format to a table with 4 columns:
Store,Item,Quantity,Details;Store2,Item2,Quantity2,Details2; (etc...)
Commas separate columns while semi-colons separate rows.
All the methods I've tried so far have been successful in splitting the data into the desired form, but the problem occurs when new data is added. When the form is submitted, it creates a new row in the next available empty row. I can't seem to find a way to automate the process without receiving cyclical dependency errors, since each order can have any amount of item entries.
Example spreadsheet:
https://docs.google.com/spreadsheets/d/1ZEWtmMiWO0Us76Z7o7GB7Salw1Rl_-1PhK6GzeOD0GM/edit?usp=sharing
The above example splits the data as desired. I cannot figure out how to make it work with the data added as a new row. I would also like to continue using sheets for its cloud functionality.
Any advice is appreciated, including entirely new ways of processing the data, whether with a script, a different remotely accessible order processing app compatible with Squarespace forms, or natively within Sheets.
You want to achieve the following conversion.
Sample formula:
=ARRAYFORMULA(SPLIT(TRANSPOSE(split(A4,";")),","))
In this formula, the cell "A4" has the input value.
You have already used the formula of =TRANSPOSE(split(A10,";")). In this answer, I used this.
For TRANSPOSE(split(A10,";")), the value is splitted with , using SPLIT and ARRAYFORMULA.
Result:
Sample script:
When you want to use Google Apps Script, you can also use the following script.
function myFunction(value) {
const values = value.split(";");
return values.splice(0, values.length - 1).map(e => e.split(",").map(f => isNaN(f) ? f : Number(f)));
}
In this case, please copy and paste the script to the script editor, and put the custom function of =myFunction(A4) to a cell.
The same result with above formula can be obtained.
References:
SPLIT
ARRAYFORMULA
split()
map()
I have some code that pulls a range of data from a source sheet in another file and copies it to a new sheet in the current file. It used to work but now that the source range is over 1000 rows, the copy fails entirely and without error (the copy doesn't occur and the code moves along). It seems as though it's because my destination sheet only has 1000 rows and trying to copy something larger than that causes it to fail.
The "Execution Transcript" doesn't give me any insight as to why it fails. As a test, I shortened the source data to less than 1000 rows and it worked again.
The chunk of my code that copies the range is this:
// COPY DATA FROM SOURCE SPREADSHEET TO TEMP SHEET IN ACTIVE SPREADSHEET
var SRange = source_s.getDataRange();
var A1Range = SRange.getA1Notation();
var SData = SRange.getValues();
temp_s.getRange(A1Range).setValues(SData);
temp_s is the destination sheet.
source_s is the source sheet.
I suppose either of two solutions could work so I could get more than 1000 rows copied to my destination sheet:
Use a type of copy function that will automatically resize the sheet to handle the size of the range, or
Find a way to extend the sheet's size prior to the copy, similar to the "Add 1000 more rows at bottom" function you normally see at the bottom of a google sheet.
I posted this as a comment, but it should probably go as an answer too.
I had declared the range on the destination sheet to be simply "A:J" which would select all the rows for those two columns. That in effect made it A1:J1000. That works if your source is less than 1000 rows, but once the source is bigger than the desination, it fails.
TO SOLVE IT I found the last row of the source data using lastrow = source_s.getLastRow() and then named my destination range as "A1:+J"+lastrow. The copy worked.
I'm trying to use a spreadsheet as a database, where each sheet would be a table and the name of a person is used as a primary key (It seems not to be the best solution, but the good spreadsheet interface makes me prefer this solution rather than trying to use ScriptDB.)
And I want to do the following: When you select a name on a sheet and press a button on the menu I added, a function performs a search in another table and a screen and shows all the results of that query in the other table, showing properties records that only that table contains (later I want to add the possibility to generate a text file from a GDocs template).
My questions is:
1) Considering this screen/panel UI has a variable length (because the record number may vary in other tables), what is the best way to create this panel/UI in Google Apps Script? (I don't want to use the Logger.log because I want to add a button to convert the query into a file)
2) In addition to this solution (a search in the resulting 2D array):
function test(){ // to test the find function with an argument, 'any' in this case
var result = findItem('any');
if(result){Logger.log(result.getA1Notation())}else{Logger.log('no luck !')};
}
function findItem(item){
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = ss.getDataRange().getValues()
for(var n = 0;n<data.length;++n){
if(data[n].indexOf(item)>-1){ // this is a "strict" find, ie the value must be the entire search item. If you want to do partial match you should compare differently...
return (ss.getRange(n+1,data[n].indexOf(item)+1)); // if found return the range. note the +1 because sheets have 1 index while arrays have 0 index
}
}
return false;// if we come to the end of sheet without result...
}
There is an alternative method to perform queries like this?
THANKS for any help!
Create a UI instance. Then a scrollable panel inside a main panel is the best way of doing this and then using array's to search through the data. I typically create header body and footer panels with the body being scrollable