Pivot or Transpose data in a google sheet using apps script - google-apps-script

I'm hoping someone can assist me with either pivoting or transposing data within a google sheet programmatically using apps script.
Below is what I've done so far. I'm pretty sure this is far from the correct/optimum way of achieving this hence me reaching out here. The data is for a survey with 3 questions. The script runs fine but doesn't account for any surveys where there where no answers selected for a certain question or for any one of the 3 questions. I'm pretty sure that's because there needs to be an else statement part of each If statement but can't figure out how to write that logic within each loop.
I also tried experimenting by converting this to and array of objects, where I tried using the ID key to to match the date, source, and three questions but couldn't get that working either.
I've attached images of what the source data looks like and what I'm trying to achieve, as well as what it currently looks like after I execute the script I currently have.
The code I've written is after the 3 images I've uploaded.
Hope I've explained this all correctly. I'd appreciate any assistance with this.
The source data looks like this:
And this is what I am trying to achieve:
This is what the data looks like once the above code has run:
function sample() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('sampleData');
var range = sheet.getRange(2,1,sheet.getLastRow()-1,sheet.getLastColumn());
var values = range.getValues();
var resultsSh = ss.getSheetByName('sampleResults');
//console.log(values);
var source = values.filter(function(row){
if(row[2] === 'Source'){
return true;
} else {
return false;
}
});
//console.log(source);
var q1 = values.filter(function(row){
if(row[2] === 'Question1'){
return true;
} else {
return false;
}
});
//console.log(q1);
var q2 = values.filter(function(row){
if(row[2] === 'Question2'){
return true;
} else {
return false;
}
});
//console.log(q2);
var q3 = values.filter(function(row){
if(row[2] === 'Question3'){
return true;
} else {
return false;
}
});
//console.log(q3);
var result1 = [];
for (i=0;i<source.length;i++){
for (j=0;j<q1.length;j++){
if(source[i][1] === q1[j][1]){
result1.push([...source[i], ...q1[j]]);
}
}
}
//console.log(result1);
var result2 = [];
for (i=0;i<result1.length;i++){
for (j=0;j<q2.length;j++){
if (result1[i][1] === q2[j][1]) {
result2.push([...result1[i],...q2[j]])
}
}
}
//console.log(result2);
var final = [];
for (i=0;i<result2.length;i++) {
for (j=0;j<q3.length;j++) {
if (result2[i][1] === q3[j][1]) {
final.push([...result2[i], ...q3[j]])
}
}
}
//console.log(final);
var data = final.map(function(row){
return [row[0].toLocaleString('en-GB').replace(/[',']/g,''), row[1], row[3], row[7], row[11], row[15], row[7] + row[11] + row[15]];
});
console.log(data);
ss.getSheetByName('Sheet16').getRange(2, 1, data.length, data[0].length).setValues(data);
}

This can be done with a single for loop that checks each row and a switch() case: to fill up the respective array elements and compute for the sum.
function sample() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('sampleData');
var range = sheet.getRange(2,1,sheet.getLastRow()-1,sheet.getLastColumn());
var values = range.getValues();
var resultsSh = ss.getSheetByName('sampleResults');
var lastRow = sheet.getLastRow();
var lastID = values[0][1];
var result = [];
var sum = 0;
// check each row
for (i=0; i<lastRow-1; i++) {
var currID = values[i][1];
if ((currID != lastID) & sum) {
result[result.length-1] = sum;
}
switch (values[i][2]) {
case 'Source':
result.push(values[i][0],values[i][1],values[i][3],'','','','');
sum = 0;
break;
case 'Question1':
result[result.length-4] = values[i][3];
sum += values[i][3];
break;
case 'Question2':
result[result.length-3] = values[i][3];
sum += values[i][3];
break;
case 'Question3':
result[result.length-2] = values[i][3];
sum += values[i][3];
break;
}
lastID = currID;
}
result[result.length-1] = sum;
// convert to 2d array
const result2d = [];
while(result.length) result2d.push(result.splice(0,7));
// put to results sheet
var resultRange = resultsSh.getRange(2,1,result2d.length,result2d[0].length);
resultRange.setValues(result2d);
}
Sample Data:
Sample Results:

Related

How to display info from other sheets, specific to text in cells

Our company works at different properties repairing appliances, I would like to build a database to search up the information on each appliance at specific properties and in their specific apt/units, I created a form to start this process, but I need help with some complex coding.
I first created a box for the property, then I created an "Apt/Unit" box. The idea is when I select a property, the units tied to that property are shown in dropdown/type searchable list in the Apt/Unit box.
I then created an "Appliance type" box. The idea is when the "Apt/Unit" is selected, it will display the dropdown/type searchable list of the appliances tied to that specific "Apt/Unit".
Then I created boxes for the info for the appliance (Brand, Model #, Serial #, & Color), this is a bit more self-explanatory - once the appliance type is selected, it will display the respective information for each box for that appliance.
Here's the link to the Google sheet: https://docs.google.com/spreadsheets/d/1JZhEYjk5xVN3uOc_Ucb8HFr6d96XQ2Q_ehAd-d_o0ME/edit?usp=sharing
Any help is appreciated!
non-scripted solution:
=IFERROR({INDEX(IFERROR(Data!A1:G1/0)); Data!A1:G1; QUERY({Data!A2:G}, "where 1=1 "&
IF(C10="",,"and lower(Col1) contains '"&LOWER(C10)&"'")&
IF(C12="",,"and Col2 = "&C12)&
IF(C14="",,"and lower(Col3) contains '"&LOWER(C14)&"'")&
IF(C16="",,"and lower(Col4) contains '"&LOWER(C16)&"'")&
IF(C18="",,"and lower(Col5) contains '"&LOWER(C18)&"'")&
IF(C20="",,"and lower(Col6) contains '"&LOWER(C20)&"'")&
IF(C22="",,"and lower(Col7) contains '"&LOWER(C22)&"'"), 0)}, {"";"no data"})
demo sheet
Here is third variant of the script:
// global variables
var SS = SpreadsheetApp.getActiveSpreadsheet();
var SHEET_USERFACE = SS.getSheetByName('Userface');
var SHEET_DATA = SS.getSheetByName('Data');
function onLoad() { reset() }
function reset() {
SS.toast('Please wait...');
SHEET_USERFACE.getRange('c9:c21').clearContent();
SHEET_USERFACE.getRange('c9:c13').clearDataValidations();
var obj = make_obj_from_data();
update_menu_prop(obj);
update_menu_unit(obj);
update_menu_type(obj);
SS.toast('The sheet has been reset');
}
function onEdit(e) {
if (e.range.getSheet().getName() != 'Userface') return;
if (e.range.columnStart != 3) return;
// Property menu
if (e.range.rowStart == 9) {
e.source.toast('Please, wait...');
SHEET_USERFACE.getRange('c11:c21').clearContent();
SHEET_USERFACE.getRange('c11:c13').clearDataValidations();
var obj = make_obj_from_data();
update_menu_unit(obj);
update_menu_type(obj);
e.source.toast('The sheet has been updated');
}
// Apt/Unit menu
if (e.range.rowStart == 11) {
e.source.toast('Please, wait...');
SHEET_USERFACE.getRange('c13:c21').clearContent();
SHEET_USERFACE.getRange('c13').clearDataValidations();
var obj = make_obj_from_data();
update_menu_type(obj);
e.source.toast('The sheet has been updated');
}
// Applicance type menu
if (e.range.rowStart == 13) {
e.source.toast('Please, wait...');
SHEET_USERFACE.getRange('c15:c21').clearContent();
var obj = make_obj_from_data();
update_brand_model_serial_color(obj);
e.source.toast('The sheet has been updated');
}
}
function make_obj_from_data() {
var data = SHEET_DATA.getDataRange().getValues().slice(1);
var obj = {};
for (let row of data) {
var [prop, unit, type, ...etc] = row;
try {
obj[prop][unit][type] = etc;
}
catch(e) {
try {
obj[prop][unit] = {}; obj[prop][unit][type] = etc;
}
catch(e) {
obj[prop] = {}; obj[prop][unit] = {}; obj[prop][unit][type] = etc;
}
}
}
return obj;
}
function update_menu_prop(obj) {
var cell = SHEET_USERFACE.getRange('c9');
try {
var list = Object.keys(obj);
set_data_validation(cell, list);
} catch(e) {
console.log('update_menu_prop(obj)');
console.log(e);
}
}
function update_menu_unit(obj) {
var prop = SHEET_USERFACE.getRange('c9').getValue();
var cell = SHEET_USERFACE.getRange('c11');
try {
var list = Object.keys(obj[prop]);
set_data_validation(cell, list);
} catch(e) {
console.log('update_menu_unit(obj)');
console.log(e);
}
}
function update_menu_type(obj) {
var prop = SHEET_USERFACE.getRange('c9').getValue();
var unit = SHEET_USERFACE.getRange('c11').getValue();
var cell = SHEET_USERFACE.getRange('c13');
try {
var list = Object.keys(obj[prop][unit]);
set_data_validation(cell, list);
if (list.length == 1) update_brand_model_serial_color(obj)
} catch(e) {
console.log('update_menu_type(obj)');
console.log(e);
}
}
function update_brand_model_serial_color(obj) {
var [prop,,unit,,type] = SHEET_USERFACE.getRange('c9:c13').getValues();
try {
var [brand, model, serial, color] = obj[prop][unit][type];
var arr = [[brand],[''],[model],[''],[serial],[''],[color]];
SHEET_USERFACE.getRange('c15:c21').setValues(arr);
} catch(e) {
console.log('update_brand_model_serial_color(obj)');
console.log(e);
}
}
function set_data_validation(cell, list) {
var rule = SpreadsheetApp.newDataValidation().requireValueInList(list).build();
cell.setDataValidation(rule);
// put the value in the cell if there is just one element in the list
if (list.length == 1) cell.setValue(list[0]);
}
Here is my sheet.
It works about that way as it does any similar interface. You select the first menu and it changes data validation for the second menu and cleans the third menu. Then you select the second menu and it changes the third one. As soon as you change the third menu it fills the rest fields.
Since you're using just the three menus and they supposed to be changed step by step I decided to 'hardcode' them. It's not the best practice and there can be problems if/when you decide to change the functionality. But for this particular case I think the 'hardcoding' is forgivable. It works relatively fast and the code is relatively readable.
Just for fun I've made it. But this is overkill:
// global variables
var SS = SpreadsheetApp.getActiveSpreadsheet();
var SHEET_USERFACE = SS.getSheetByName('Userface');
var SHEET_DATA = SS.getSheetByName('Data');
function onLoad() { reset() }
function onEdit(e) {
if (e.range.getSheet().getName() != 'Userface') return;
if (e.range.columnStart != 3) return;
if (![9,11,13,15,17,19,21].includes(e.range.rowStart)) return;
e.source.toast('Please, wait...');
set_filter(e.range.offset(0,-1).getValue(), e.value);
set_all_menus();
e.source.toast('The sheet has been updated');
}
function reset() {
SS.toast('Please wait...');
try { SHEET_DATA.getFilter().remove() } catch(e) {}
SHEET_USERFACE.getRange('c9:c21').clearContent().clearDataValidations();
set_all_menus();
SS.toast('The sheet has been updated');
}
function set_all_menus() {
var data = SHEET_DATA.getDataRange().getDisplayValues().filter((_,i) => !SHEET_DATA.isRowHiddenByFilter(i+1));
set_menu(data, 'b9', 'c9');
set_menu(data, 'b11', 'c11');
set_menu(data, 'b13', 'c13');
set_menu(data, 'b15', 'c15');
set_menu(data, 'b17', 'c17');
set_menu(data, 'b19', 'c19');
set_menu(data, 'b21', 'c21');
}
function set_menu(data, title, cell) {
var menu_title = SHEET_USERFACE.getRange(title).getValue();
var menu_cell = SHEET_USERFACE.getRange(cell);
var col_index = data[0].indexOf(menu_title);
var menu_list = [...new Set([...data.map(e => e[col_index])])].slice(1);
var menu_rule = SpreadsheetApp.newDataValidation().requireValueInList(menu_list).build();
menu_cell.setDataValidation(menu_rule);
}
function set_filter(column_title, value) {
// get all the data and col index
var [header, ...data] = SHEET_DATA.getDataRange().getValues();
var col_index = header.indexOf(column_title);
// unhide all values of the given column
var clear = SpreadsheetApp.newFilterCriteria().setHiddenValues([]).build();
var range = SHEET_DATA.getDataRange();
var filter = range.getFilter() || range.createFilter()
filter.setColumnFilterCriteria(col_index+1, clear);
// get the values to hide
var col_data = data.map(e => e[col_index]);
var filtered = col_data.filter( (e, i) => e != value && SHEET_DATA.isRowHiddenByFilter(i+1) );
var to_hide = col_data.filter( e => e != value );
var hidden = [...new Set([...filtered, ...to_hide])];
// hide the values with the filter
var criteria = SpreadsheetApp.newFilterCriteria().setHiddenValues(hidden).build();
var range = SHEET_DATA.getDataRange();
var filter = range.getFilter() || range.createFilter()
filter.setColumnFilterCriteria(col_index+1, criteria);
}
Here is the sheet.
It works quite slow. I'd propose to use the native filters instead. Basically the script turns on and off the filters an changes data validation for the dropdown menus respectively.
Update
Here another version of the script. It works much faster but it uses the 'helper sheet' to store temporary data (the filtered table). You can hide the 'helper sheet' if you want.
// global variables
var SS = SpreadsheetApp.getActiveSpreadsheet();
var SHEET_USERFACE = SS.getSheetByName('Userface');
var SHEET_DATA = SS.getSheetByName('Data');
var SHEET_HELPER = SS.getSheetByName('Helper'); // the hidden sheet with temp data
var PROPERTY_LIST = [...new Set(SHEET_DATA.getRange('a2:a').getValues().flat())]; // 'Property' list
var DATA_OBJ = {};
function onLoad() { reset() }
function onEdit(e) {
var {range, source, value} = e;
if (range.getSheet().getName() != 'Userface') return;
if (range.columnStart != 3) return;
if (![9,11,13,15,17,19,21].includes(range.rowStart)) return;
source.toast('Please, wait...');
// reset whenever the first menu is changing
if (range.rowStart == 9) {
reset();
source.getRange('c9').setValue(value);
}
var col_header = range.offset(0,-1).getValue();
update_sheet_helper(col_header, value);
update_all_dropdown_menus();
source.toast('The sheet has been updated');
}
function reset() {
SS.toast('Please wait...');
// copy data from SHEET_DATA to SHEET_HELPER
SHEET_USERFACE.getRange('c9:c21').clearContent().clearDataValidations();
SHEET_DATA.getDataRange().copyTo(SHEET_HELPER.clearContents().getRange(1,1));
update_data_obj();
update_all_dropdown_menus();
SS.toast('The sheet has been updated');
}
// make DATA_OBJECT from SHEET_HELPER
function update_data_obj() {
DATA_OBJ = {};
var [header, ...data] = SHEET_HELPER.getDataRange().getValues();
for (let i in header) DATA_OBJ[header[i]] = data.map(e => e[i]);
DATA_OBJ['Property'] = PROPERTY_LIST; // let 'Property' list will be full always
}
// remove from SHEET_DATA_HELPER all the rows
// that have no given value in column with given title
function update_sheet_helper(col_title, value) {
var [header, ...data] = SHEET_HELPER.getDataRange().getValues();
var col_index = header.indexOf(col_title);
data = data.filter(k => k[col_index] == value);
var table = [header, ...data];
SHEET_HELPER.clearContents().getRange(1,1,table.length, table[0].length).setValues(table);
update_data_obj();
}
function update_all_dropdown_menus() {
SHEET_USERFACE.getRange('b9:c21').getValues().forEach((row,i) => {
if (row[0] != '') set_data_validation(DATA_OBJ[row[0]], 'c' + (i+9));
});
function set_data_validation(data, cell_address) {
var menu_list = [...new Set([...data])]; // remove duplicates from the array
var menu_rule = SpreadsheetApp.newDataValidation().requireValueInList(menu_list).build();
var cell_range = SHEET_USERFACE.getRange(cell_address)
cell_range.setDataValidation(menu_rule);
if (menu_list.length == 1) cell_range.setValue(menu_list[0]);
}
}
The sheet is here.

Search match to autofill text inputs google sheets

i'm trying to do a match and return function: search barcode and a size value in a sheet, and add it to a textbox in a sidebar
something went wrong - script do not return any values
var barcodeBox = document.getElementById("bcdn");
var typeBox = document.getElementById("stype");
var sizeBox = document.getElementById("size");
document.getElementById("butn").addEventListener("click",addRecord);
document.getElementById("bcdn").addEventListener("input",getStype);
function getStype(){
var bCode = document.getElementById("bcdn").value;
if(bCode.length === 10){
google.script.run.withSuccessHandler(updateSize).getSize(bCode);
}else{
document.getElementById("size").value = "-";
M.updateTextFields();
} // end Else
} // end getStype
function updateSize(size){
document.getElementById("size").value = size;
M.updateTextFields();
}
this is gs code
function getSize(bCode){
var ws = SpreadsheetApp.getSheetByName("Barcode List");
var data = ws.getRange(1,1,ws.getLastRow(),2).getValues();
var bCodeList = data.map(function(r){return[0];});
var sizeList = data.map(function(r){return[1];});
var position = bCodeList.indexOf(bCode);
if(position > -1){
return sizeList[position].toFixed(2);
}else{
return "";
}
bCode seems to be just a value but bCodeList is an array of arrays so bCodeList.indexOf(bCode) is probably always going to be -1
Maybe change this var bCodeList = data.map(function(r){return[0];});
to this var bCodeList = data.map(r => 0);
Of course sizeList has the same problem
Missing get spreadsheet
Wrong return of map
Not converting [][] into [] for indexOf
var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Barcode List");
var data = ws.getRange(1,1,ws.getLastRow(),2).getValues();
var bCodeList = data.map(function(r){return r[0];}).flat();
var sizeList = data.map(function(r){return r[1];}).flat();

Copy last updated row from Sheet1 to Sheet2

data.forEach(function(f) {
if (~f.indexOf(searchtext)) {
ar.push(f);
}
});
return ar;
}
function isUpdated(data) {
var arrayLength = data.length;
var updated = false;
var oldData = search(data[1]);
for (var i = 1; i < arrayLength; i++) {
if(oldData[0][i] != data[i]){
updated = true;
break;
}
}
return updated;
}
function update(data){
data = JSON.parse(data)
const tag = data[1].toString().trim().toUpperCase()
const ws = SpreadsheetApp.openById(SS_ID).getSheetByName(SN_DATA)
const values = ws.getDataRange().getValues()
const index = values.findIndex(v => v[1].toString().trim().toUpperCase() === tag)
if (index === -1) return false
ws.getRange(index + 1, 1, 1, data.length).setValues([data])
return true
}
Hello expert I'm able to write a code to search and update using Web App. google Sheet Sheet1 can anyone help ,e how to copy my last edited/updated rows to sheet2? I want to add row function every time the sheet1 has been updated the updated column will logs to sheet2? code has been provide above

Running script for multiple search terms found on different sheet

I'm trying to get a SCORESHEET to populate from a REPORTSHEET, using a REFERENCESHEET to collate search terms and destination cells.
The script I'm running is as below. The idea is that the script finds searchDate's in the REFERENCESHEET and uses them to locate data columns in the REPORTSHEET:
function superAuto() {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
var scorecard = SpreadsheetApp.openById('SCORESHEET');
var scorecardData = scorecard.getDataRange().getValues();
var tExpenses = "Total Expenses";
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log (searchDate)
}
var column = columnfinder(searchDate);
for (var a = 0; a < referenceData.length; a++) {
var refRow = referenceData[a];
for (var i = 0; i < reportData.length; i++) {
var row = reportData[i];
if (row[0] == tExpenses && refRow[0] == searchDate) {
scorecard.getRange(refRow[5]).setValue(row[column]);
}
}
}
}
function columnfinder(find) {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
for(var j=0, jLen=reportData.length; j<jLen; j++) {
for(var k=0, kLen=reportData[0].length; k<kLen; k++) {
if(find == reportData[j][k]) {
Logger.log(k);
return (k);}
}
}
}
Broadly speaking, the code works, as if I define searchDate as one of the terms I'm looking for (e.g. Jan-21) it all works fine. The issue is that it doesn't seem to be doing so when finding multiple search terms - and therefore populating multiple rows - as per:
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log (searchDate)
}
The log tells me that it's finding searchDate's in the REFERENCESHEET, but it's not able to run them through function columnfinder (I get no logs for the second logger).
I suspect the answer lay somewhere in an earlier great answer I received to an earlier version of this idea - How to return multiple column values for setValue - but I've not been able to make it fit. Any thoughts?
EDIT: Please find a sample REFERENCESHEET & REPORTSHEET for more info:
The log tells me that it's finding searchDate's in the REFERENCESHEET,
but it's not able to run them through function columnfinder (I get no
logs for the second logger)
You don't execute columnfinder inside the for loop.
Try this:
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log(searchDate);
columnfinder(searchDate); // modified code
}
and you will get both logs.
Sorry if I misunderstood your question.
You need to use have it assigned to array since you are returning possible multiple columns/dates:
function superAuto() {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
var scorecard = SpreadsheetApp.openById('SCORESHEET');
var scorecardData = scorecard.getDataRange().getValues();
var tExpenses = "Total Expenses";
var searchDates = [];
for (n = 0; n < referenceData.length; ++n) {
searchDates.push(referenceData[n][0])
}
var columns = columnfinder(searchDates);
columns.forEach(function (column, index) {
referenceData.forEach(function (refRow) {
reportData.forEach(function (row) {
if (row[0] == tExpenses && refRow[0] == searchDates[index]) {
scorecard.getRange(refRow[5].toString()).setValue(row[column]);
}
});
});
});
}
function columnfinder(dates) {
var report = SpreadsheetApp.openById('REPORTSHEET');
var reportData = report.getDataRange().getValues();
var reference = SpreadsheetApp.openById('REFERENCESHEET');
var referenceData = reference.getDataRange().getValues();
var columns = [];
dates.forEach(function (date) {
reportData.forEach(function (row, i) {
row.forEach(function (col, i) {
if (date == reportData[i][j]) {
columns.push(reportData[i][j]);
}
});
});
});
return columns;
}
I changed some variables into proper variable names to avoid confusion.
Additionally, if it doesn't work, you might need to share a proper visualization of the data, or better yet, provide some sample sheet we can work on for us to be able to give you a better and tested answer.
Thanks Marios, that's twice in a week. Much appreciated.
Slight adaptation, in order for it to populate SCORECARD I needed to bring everything into the for loop, as below:
for(n=0;n<referenceData.length;++n){
var searchDate = referenceData[n][0] ;
Logger.log (searchDate)
var column = columnfinder(searchDate);
for (var a = 0; a < referenceData.length; a++) {
var refRow = referenceData[a];
for (var i = 0; i < reportData.length; i++) {
var row = reportData[i];
if (row[0] == tExpenses && refRow[0] == searchDate) {
scorecard.getRange(refRow[5]).setValue(row[column]);
}
}
}
}

Sheet order not respecting index in google sheets in chrome

This spreadsheet has a script that updates the tabs and labels them in response to the week ending date entered. It also rearranges them based on the selected last day of the week (Saturday or Sunday for example). The spreadsheet formulas calculate the order of the days and the day and day of the month labelling. These are read into the script from named ranges.
It works fine on my IOS devices and sometimes works properly in chrome desktop. But (in chrome desktop) it gets into a mode where one sheet is always out of position. In the example, I label the tabs with the sequence position (0 to 6) and the returned sheet index, so it's clear that the problem is alignment between the object model and the rendering. As you can see in the following pic, the Monday sheet is out of position, if I change the end of week day again, it will be the one out of position again.
How can I force google sheets to respect the sheet index?
I tried flush but no joy. The only way I can get it to line up reliably is to close and re-open the sheet.
function onEdit(e) {
var source = e.range;
var ss = SpreadsheetApp.getActiveSpreadsheet();
if(!qualifiedSource(e.range, ["selectedDate", "lastDayOfWeek"].map(function(n) {
return ss.getRangeByName(n);
}))) return;
var fmtedNames = WeekEndingDate2();
// add indexing to the sheet names
ss
.getSheets()
.filter(function(sht) { return fmtedNames.indexOf(sht.getName()) != -1 })
.forEach(function (s, i) { s.setName(s.getName() + ":" + i + ":" + s.getIndex()); });
}
function qualifiedSource (source /*range*/, target /*range | range[]*/) {
if(!isArray(target)) target = [target];
return target.some(function(t) {
return source.getSheet().getName() == t.getSheet().getName() && source.getA1Notation() == t.getA1Notation();
});
}
function WeekEndingDate2() {
var _wb = SpreadsheetApp.getActiveSpreadsheet();
var _days = _wb.getRangeByName("daysOfWeek");
var _daysFmt = _wb.getRangeByName("daysOfWeekFmt");
return (function _update() {
var daySheets = SheetsCollection();
var days = _days.getValues()[0];
var daysFmt = _daysFmt.getValues()[0];
daySheets
.Add(days.map(namesToSheets))
.Sort(daysFmt);
return daysFmt;
function namesToSheets(d, i) {
var allSheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var targetSheet;
allSheets.some(function(sht) {
return targetSheet = sht.getName().indexOf(d) === 0 ? sht : null;
});
Logger.log(Utilities.formatString("%s\t%s", d, targetSheet.getName()));
if (targetSheet == null)
throw new Error("Error: Missing sheet for " + d);
return targetSheet.setName(daysFmt[i]);
}
})();
}
function SheetsCollection () {
var _sheets = {}; // hash of WrappedSheets
var _maxIndex = 0;
function _hash (n) {
return n.replace(" ", "_");
}
function _addItem (s /*worksheet*/) {
_sheets[_hash(s.getName())] = WrappedSheet(s);
_maxIndex = Math.max(_maxIndex, s.getIndex())
}
function _add (s /*worksheet | worksheet[]*/) {
if(Array.isArray(s))
s.forEach(_addItem);
else
_addItem(s);
return this;
}
function _sort ( sortOrder /*range | string[]*/, delay /* int milliseconds */) {
var sortedNames = sortOrder.getValues ? sortOrder.getValues()[0] : sortOrder;
var namesLength = sortedNames.length;
var i, sht;
for each (var name in sortedNames) {
Logger.log(name);
_sheets[_hash(name)].MoveTo(_maxIndex);
if(delay) Utilities.sleep(delay);
}
return this;
}
return {
Add: _add,
Sort: _sort
}
}
function WrappedSheet(sheet /*string || sheet*/) {
var _wb = SpreadsheetApp.getActive();
var _sheets = _wb.getSheets();
var _shtName = typeof sheet == "string" ? sheet : sheet.getName();
function _moveTo (to /*integer*/) {
var insertAt = to;
var actSht = _wb.getActiveSheet();
var maxAttempts = 10;
var attempt = 1;
var before = this.Sheet.getIndex();
Logger.log(listSheets(""));
_wb.setActiveSheet(this.Sheet);
_wb.moveActiveSheet(insertAt);
_wb.setActiveSheet(actSht);
Logger.log("%s -> %s after %s", this.Name, this.Sheet.getIndex(), attempt -1);
Logger.log(listSheets(""));
}
return {
MoveTo: _moveTo,
get Sheet() { return _wb.getSheetByName(_shtName); },
get Position() { return _wb.getSheetByName(_shtName).getIndex(); },
Name: _shtName
}
}
I faced the exact same problem as you with a very similar functioning script. Unfortunately, there is no 'fix' for this issue, it seems to be a caching issue with the browser. The only way around it I found was to refresh the window twice or close and reopen the spreadsheet and then the tabs appear in the correct order.