Change order of columns spreahseet - google-apps-script

I just want to confirm if there is any inbuilt function is google apps script to reorder the columns in the spreadsheet or do I have to do something like this -
var r = currentSheet.getRange(1, 1, currentSheet.getLastRow() - 1, currentSheet.getLastColumn()).getValues();
for(var i1=0, dLen=r[0].length; i1<dLen; i1++) {
//something to reorder
}
Please help!

it depends. if you just want to present the data in a different order, just use the built-in "=query" cell function, selecting the rows in the order you want.
query has some limitations thou. for example each column must have a consistent type on all rows (date, number, string etc). there arr other built-in functions that do similar things.
if you actually want to change the source range, you need to do it by getting the entire range array values, transforming it (possibly needing to take care of functions too) and writting the entire new array in a single range write.

Related

Range.SetValues() does not insert data on one sheet, on the other works. What is the reason?

I have a GoogleSheet with basically two sheets, which are very similar in terms of data collected.
I need to calculate same values for both sheets, but source data is in different columns.
Therefore I created three files in AppsScript:
Common.gs - with common function definitions
sheet1.gs
sheet2.gs - both sheet1 and sheet2 have only definitions of proper ranges in particular columns and one function to run script, which essentially calls functions defined in Common.gs, like so in sheet1.gs:
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("sheet1")
var createdColumn = sheet.getRange("E2:E").getValues()
var ackColumn = sheet.getRange("G2:G").getValues()
var resColumn = sheet.getRange("I2:I").getValues()
var timeToAckColumn = sheet.getRange(2,14,ackColumn.length,1)
var timeToResColumn = sheet.getRange(2,15,resColumn.length,1)
var yearAndWeekRange = sheet.getRange(2,16,createdColumn.length,2)
function calculateMetricsSheet1() {
calculateTimeDiff(createdColumn, ackColumn, timeToAckColumn)
calculateTimeDiff(ackColumn, resColumn, timeToResColumn)
calculateWeek(createdColumn, yearAndWeekRange)
}
example function implementation (they are basically very similar with minor differences):
function calculateWeek(createdColumn, yearAndWeekRange) {
var arrData = []
for(var i=0;i<createdColumn.length;i++) {
if(createdColumn[i][0].toString()=="") {
arrData.push(["",""])
continue
}
var createdDate = new Date(createdColumn[i][0])
var year = createdDate.getFullYear()
var week = Utilities.formatDate(createdDate, "GMT+1", "w")
arrData.push([year, week])
}
yearAndWeekRange.setValues(arrData)
}
the sheet2.gs is basically different column definitions, the functions called within calculateMetricsSheet2() are the same.
So what is the problem?
The script works perfectly fine for sheet2.gs, but for sheet1.gs it does collect proper data, calculates proper data, but the data does not appear in proper columns after Range.setValues() call.
No exceptions or errors appear in the console.
Documentation does not provide any kind of information what could be the problem.
I have really ran out of ideas what could be the cause of the issue.
Does anyone have any idea what is going on?
edit: It may be useful to put emphasis on the fact that each script runs function calling 3 other functions -> all of them end with Range.setValues({values}). And for one sheet all of them work, and for the other - none.
That's the reason I assume there is something wrong with the sheet itself, maybe some permissions/protection? But I couldn't find anything :(
edit2: I modified my code to iterate through the sheet 10 rows at a time, because I thought maybe when I get a whole column, something bad happens with data and breaks setValues() function.
Unfortunately - even if my code iterated 1 row at a time, it still did not work on sheet1, but worked on sheet2. So not a data problem.
The code you show always puts values in yearAndWeekRange which is always in the 'sheet1' sheet. To put the data into another sheet, you need to change the target range appropriately.
The dimensions of the range must match the dimensions of the array you put there. Use this pattern:
yearAndWeekRange.offset(0, 0, arrData.length, arrData[0].length).setValues(arrData);
I found out what is the problem.
Two scripts were pretty identical, even with naming of variables - ie ackColumn, resColumn etc.
Those were stored as a global variables, so even if I was running script1.gs, it used global variables from script2.gs, effectively writing proper data to wrong sheet.
separating global variables names fixed the issue.
Perhaps a rookie mistake, but I missed the fact, that if I have a variable defined outside any function, it becomes global and could be overwritten from other file

Sort with a button for a google spreadsheet

I want a button in my spreadsheet which sorts based on 3 criteria.
I tried about 20 different ways of writing the syntax, but none of those worked.
I'd be glad if someone could tell me what is wrong.
function sort1() {
sort({C3:K12;9;FALSE;3;TRUE;8;FALSE})
}
If strict adherence to your predefined sorting criteria is not important, and you are happy to let users adjust the sort by themselves, you can achieve this result quite simply by selecting the region of data of interest, and then creating a "filter" on it.
A Google Sheets "filter" is really a rich sorting and filtering mechanism. Implemented this way, the end user can achieve the exact sort you mentioned by selecting the 3rd sort criterion (column 8 in your example), the 2nd (column 3), and then the 1st (column 9).
Example of a filter applied to a region of a sheet:
function sort1() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getActiveSheet();
var rg=sh.getRange(3,3,10,9);
rg.sort([{column:11,ascending:false},{column:5,ascending:true},{column:10,ascending:false}]);//The sort options is an array of objects but the array can be left for just one.
}
Reference
You can't run a Sheets function from an Apps Script like that. If you are writing the script yourself, you will need to get a range object and sort that. You may want to start by recording a macro, which will write the script for you (Tools > Macros). Then you can look at the script and see what it is doing. Once the script is working the way you want, here's a good post on running a script from a button: https://www.benlcollins.com/apps-script/google-sheets-button/
Since you plan on doing 3 types of sorting, you could use three buttons, each for the wanted type of sort. Then you could attach to each one the type of sorting, like presented below.
The global variables sheet and range.
var sheet = SpreadsheetApp.getActive().getActiveSheet();
var range = sheet.getRange(3, 2, 11, 10);
The function sort1 sorts your data by points, descending:
function sort1() {
range.sort([{column:11,ascending:false}]);
}
The function sort2 sorts your data by games, ascending:
function sort2() {
range.sort([{column:5,ascending:true}]);
}
The function sort3 sorts your data by difference, descending:
function sort3() {
range.sort([{column:10,ascending:false}]);
}
Afterwards, in order to assign the above functions, you should create three buttons, like this:
After creating the buttons, you should assign to each one of the them the above functions:
SORT BY POINTS button -> sort1;
SORT BY GAMES button -> sort2;
SORT BY DIFF button -> sort3;
If you want to use all the sorting criteria in one instruction, you should just use all of them in the same sort method.
range.sort([{column:11,ascending:false},{column:5,ascending:true},{column:10,ascending:false}]);
Furthermore, I suggest you check the above links since they might be of help in your future development:
Sheet Class Apps Script;
Range Class Apps Script.

How to use Google Apps Script to bring in Spreadsheet values efficiently

I currently use the following code to bring in many values from a spreadsheet
myValues["TitleText"] = ss.getRange('B9').getValue();
I then display the values in a index.html form using the same Google Apps Script like
<div id="title"><?= data.TitleText ?></div>
I was wondering if I should use something else. Maybe like an ArrayFormula?
I guess it looks to me like I am taking many trips back to the sheet to get all of the values that I need. It would be nice to get all values in one visit thus speeding up the loading and processing of the form.
If a one trip approach is possible; what would it look like?
If the data is in an array, there is no "built-in" way to associate one value with another value. With an object, you can associate the "key" with the "value". There are ways to do the same thing with arrays, but it's a lot "trickier". Whether you should use an array or an object, depends on the "bigger picture". If you want to associate a title with a value, and the position of the title and value could be changing in the spreadsheet, then it MIGHT make sense to compile the data in an object before sending it to your HTML. But then there is the issue of performance. It can be better to construct the HTML in the server code, and then send the HTML string back to the client, and not just the data.
If you have lots of scriptlets, you might try having just one printing scriptlet, create the HTML string in the ".gs" server code, and then send the HTML back instead of sending just the data back.
If you compile the data in some special format, send the data, then you need to unscramble the data and construct the HTML, that might be more processing than just building the HTML from the start in the server code.
The getRange() method has four different parameter configurations. You are using the a1Notation variation. I would use this variation:
getRange(starting row, starting column, number of Rows, number of Columns)
Then you can use code like this:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('the sheet name');
var allData = sheet
.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn())
.getValues();
If you only want to get the values in column B, starting in row 2, you can do this:
var colB_Data = sheet
.getRange(2, 2, sheet.getLastRow())
.getValues();
The getValues() method returns a two dimensional array. But if you are only getting one column of data, all the inner arrays only have one element. If you need a one dimensional array, you can (in this case) convert the two dimensional array to a one dimensional array with:
colB_Data = colB_Data.toString().split(",");

Update script cell references when columns are moved

We're migrating a lot of our business logic to scripts behind the scenes, but I'm worried that they'll be much more fragile when columns move.
On Sheet Updates Automagically
For example, If I have a formula on a spreadsheet like this:
=If(A1=5,"Yes","No")
And then I Insert 1 Column Left of A, the formula will be automatically updated like this:
=If(B1=5,"Yes","No")
Apps scripts doesn't update
For example, if I have the formula in the script section:
function myFunction() {
var value = SpreadsheetApp.getActiveSheet().getRange("A1").getValue();
var output = (value == 5) ? 'Yes' : 'No';
Logger.log(output);
}
It will not update when the sheet changes.
Q: How can I get stable references in the code behind for columns that could potentially move?
This is a general problem when hardcoding strings or numbers in code.
In general the javascript parser can't tell which strings might be used on a sheet function call. Its sometimes not trivial to solve.
Two approaches are:
If the columns/cells/ranges are known beforehand, use named ranges:
Define a named range and use NamedRange in code. Use the range to directly write to it or query its row/column position.
Another for column based ranges like yours is that your code does this naming manually by using the column header as the column names. Code uses those names and reads the header to build the mapping.

Is it possible to define a new function in Google-docs spreadsheet?

Is it possible to define a function in Google Spreadsheets that can be used in any cell?
It would be helpful if I could define and use functions that refer to other cells in the same way that I can use native functions, e.g. by entering =myfunction(C1, C2, C3)
Yes - there's a tutorial. Just use javascript functions by name in your spreadsheet. Define them using Tools > Script editor.
Watch out for name restrictions; I was confused by the behavior of functions that I created with names like "function x10() {}" not being found. Renaming to a longer name fixed it. There are probably documented rules for what isn't allowed, but I don't know where they are.
I am a "newbee". But is is my experience that you can only access a "cell"
via the "range" object. You must define the range as a single cell.
For example "A1:A1", will give you access the the cell at "A1".
A RANGE is an object associated to a "SHEET".
A SHEET is an object associated to a "SPREADSHEET".
Here is some sample code to access cell A1 in the current active sheet:
var cell_A1 = SpreadsheetApp.getActiveSheet().getRange("A1:A1");
From here you can pass the object like any other parameter.
myFunction(cell_A1);
The receiving function must "know" that it is dealing with a "range".
It can only access its values by calling "methods" associated to the
"range" object.
Be careful! A "range" can consist of more than one cell. Your called
function should test to see that it is working with a single cell.
If you pass a range of more than one cell, your function might not
act in the way you expect.
The two methods of a range object: "getNumRows()" and "getNumColumns()"
returns the numbers of Rows and Columns in a range object.
In general, if you use methods that are limited to changing or accessing
a single cell, and operate on a larger range set, the function will only be
performed on the upper-left cell member. But be careful. While you
might assume a method will only change a single cell, it may actually
affect all cells in the range. Read the documentation closely.
There is another method to obtain a range of a single cell. Its instruction
looks like this:
var cell_B2 = SpreadsheetApp.getActiveSheet().getRange(2, 2, 1, 1).
The first two parameters tell the "getRange" function the location of the
cell (in row, column format). The second two parameters define the number of
"rows" and "columns" to associated with the range. By setting them both to
"1", you access a single cell.
Hope this helps.