Google Sheets formula to determine if text in a cell is italic - google-apps-script

Is it possible in Google Sheets to run a formula to determine if the text in a cell is italic?
Something like isItalic(a1) which would return TRUE / FALSE, so that the column could be filtered on?

You could also do something like the following in Google Sheets.
The .getFontStyle() method might be what you're looking for.
var ID = 'yourDocumentID';
var name = 'nameOfYourSheet';
function myFunction() {
var sourceSheet = SpreadsheetApp.openById(ID); // Selects your Source Spreadsheet by its ID
var ssheet = sourceSheet.getSheetByName(name); // Selects your Source Sheet by its Name
var range = ssheet.getRange(6, 10) // Selects J6 (just as an example)
var fontStyle = range.getFontStyle(); // Tells you the font style
ssheet.getRange(1,1).setValue(fontStyle); // writes the font style into A1
var returnBoolean = range.getFontStyle() === 'italic' ? true : false; // checks if getFontStyle is true or false
ssheet.getRange(2,1).setValue(returnBoolean); // writes the boolean into A2
}
A link to the docs can be found here.

=CHECKSTYLE(A1, "italic")
function CHECKSTYLE(range, styleElement) {
var arr = [],
ret;
var styleEl = ["line-through", "underline", "italic", "bold"]
var range = SpreadsheetApp.getActiveSheet()
.getRange(range);
var styles;
if (styleEl.indexOf(styleElement) == 2) {
styles = range.getFontStyles()
} else if (styleEl.indexOf(styleElement) == 3) {
styles = range.getFontWeights()
} else if (styleEl.indexOf(styleElement) != -1) {
styles = range.getFontLines();
} else {
throw "the 2nd parameter can only be " + styleEl.toString()
.replace(",", ", ")
}
styles.reduce(function (a, b) {
return a.concat(b);
})
.forEach(function (el) {
el === styleElement ? arr.push(["TRUE"]) : arr.push(["FALSE"])
});
range.getNumRows() == 1 ? ret = transpose(arr) : ret = arr;
return ret;
}

// In cell place =ISSTYLE((CELL("address",D4)&":"&CELL("address",D4)), "italic")
function ISSTYLE(pRange, styleElement) {
// thanks to Https://stackoverflow.com/questions/54308576/google-sheets-formula-to-determine-if-text-in-a-cell-is-italic
// thanks to https://webapps.stackexchange.com/questions/10629/how-to-pass-a-range-into-a-custom-function-in-google-spreadsheets/92156
var range = SpreadsheetApp.getActiveSpreadsheet().getRange(pRange);
return range.getFontStyle() == styleElement;
}

Related

how to copy a range with groups information?

I copy "range" from one sheet to another using range.copyTo(..) function, it doesnt copy group information
var destSheet = some_sheet;
var sourceRange=SpreadsheetApp.getActiveSheet().getRange("A1:H10");
sourceRange.copyTo(destSheet.getRange(1,1));
Values, format, formula are copied. Not groups
Issue and workaround:
In the current stage, it seems that the dimension groups cannot be directly copied by copyTo method. So, in order to copy the dimension groups, it is required to retrieve the dimension groups and put them.
When Sheets API is used, the dimension groups can be retrieved as an object. Using this, in this answer, I would like to propose to use Sheets API. When this is reflected in your script, it becomes as follows.
Modified script:
Before you use this script, please enable Sheets API at Advanced Google services. And, please set your destination sheet name.
function deleteDimensionGroups_(sheetId, sheet) {
var obj = [...(sheet.rowGroups || []), ...(sheet.columnGroups || [])];
if (obj.length == 0) return [];
return obj
.sort((a, b) => a.depth < b.depth ? 1 : -1)
.map(o => {
o.range.sheetId = sheetId;
delete o.depth;
return { deleteDimensionGroup: o };
});
}
// Please run this function.
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssId = ss.getId();
var srcSheet = ss.getActiveSheet();
var dstSheet = ss.getSheetByName("Sheet2"); // Please set your destination sheet name.
var sourceRange = srcSheet.getRange("A1:H10");
sourceRange.copyTo(dstSheet.getRange(1, 1));
// Copy dimension groups.
var { sheets } = Sheets.Spreadsheets.get(ssId, { ranges: [srcSheet.getSheetName(), dstSheet.getSheetName()] });
var sheet = sheets[0];
var obj = [...(sheet.rowGroups || []), ...(sheet.columnGroups || [])];
if (obj.length == 0) return;
var sheetId = dstSheet.getSheetId();
var reqs = deleteDimensionGroups_(sheetId, sheets[1]);
var requests = [...reqs, ...obj
.sort((a, b) => a.depth > b.depth ? 1 : -1)
.map(o => {
o.range.sheetId = sheetId;
delete o.depth;
return { addDimensionGroup: o };
})];
Sheets.Spreadsheets.batchUpdate({ requests }, ssId);
}
Note:
In this sample script, the existing dimension groups are deleted from the destination sheet and the new dimension groups are copied. If you don't want to delete the dimension groups in the destination sheet, please modify the above script as follows.
From:
var reqs = deleteDimensionGroups_(sheetId, sheets[1]);
To:
var reqs = [];
References:
Method: spreadsheets.batchUpdate
AddDimensionGroupRequest
Added:
From your following reply,
It's full of informations. Trying to adapt it and create a copyToWithGroup(sourceRange, destRange).. not so easy at my level. But I have materials in your answer to work with.
If you want to limit the range using var sourceRange = srcSheet.getRange("A1:H10");, how about the following sample script?
Sample script:
function deleteDimensionGroups_(sheetId, obj) {
if (obj.length == 0) return [];
return obj
.sort((a, b) => a.depth < b.depth ? 1 : -1)
.map(o => {
o.range.sheetId = sheetId;
delete o.depth;
return { deleteDimensionGroup: o };
});
}
// Please run this function.
function main() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssId = ss.getId();
var srcSheet = ss.getActiveSheet();
var dstSheet = ss.getSheetByName("Sheet2"); // Please set your destination sheet name.
var sourceRange = srcSheet.getRange("A1:H10"); // In this sample, this range is used.
sourceRange.copyTo(dstSheet.getRange(1, 1));
// Copy dimension groups in "sourceRange".
var startRowIndex = sourceRange.getRow() - 1;
var endRowIndex = startRowIndex + sourceRange.getNumRows();
var startColumnIndex = sourceRange.getColumn() - 1;
var endColumnIndex = startColumnIndex + sourceRange.getNumColumns();
var { sheets: [src, dst] } = Sheets.Spreadsheets.get(ssId, { ranges: [srcSheet.getSheetName(), dstSheet.getSheetName()] });
var check = f => f.filter(({ range }) => {
if (range.dimension == "ROWS" && range.startIndex >= startRowIndex && range.endIndex <= endRowIndex) {
return true;
} else if (range.dimension == "COLUMNS" && range.startIndex >= startColumnIndex && range.endIndex <= endColumnIndex) {
return true;
} else {
return false;
}
});
var [src2, dst2] = [src || [], dst || []].map(e => {
var temp = [];
if (e.rowGroups && e.rowGroups.length > 0) temp = [...temp, ...check(e.rowGroups)];
if (e.columnGroups && e.columnGroups.length > 0) temp = [...temp, ...check(e.columnGroups)];
return temp;
});
if (src2.length == 0) return;
var sheetId = dstSheet.getSheetId();
var reqs = deleteDimensionGroups_(sheetId, dst2);
var requests = [...reqs, ...src2
.sort((a, b) => a.depth > b.depth ? 1 : -1)
.map(o => {
o.range.sheetId = sheetId;
delete o.depth;
return { addDimensionGroup: o };
})];
Sheets.Spreadsheets.batchUpdate({ requests }, ssId);
}

how do add an additional filter function in google appscript to reference a column and searching for text "TMO"

Here is the code I have for searching by a specific color. I would like to then like to also filter by the word TMO in column K
function TmoSuspended () {
var ss = SpreadsheetApp.getActive().getSheetByName("Boost Prod Account Info")
/*var cell_color = ss.getRange("A4").getBackground(); #fff2cc
Logger.log(cell_color);*/
var last_row = ss.getLastRow();
var array = ss.getRange(1, 1, last_row).getBackgrounds();
var allRows = ss.getRange("A:A");
ss.unhideRow(allRows);
for (i=8 ;i <= last_row;i++){
if(array[i] != "#f1c232")
{
ss.hideRows(i+1)};
}
}
As what I have understood from your concern, you may try this code:
This will hide rows with color #f1c232 in column A and "TMO" word in column K.
Try:
function TmoSuspended() {
var ss = SpreadsheetApp.getActive().getSheetByName("Boost Prod Account Info")
var array = ss.getRange("A:A").getBackgrounds();
//Checker of color (""#f1c232"") on column A and return true if not equal to blanks and word 'TMO'
try { var res = array.map((color, index) => { return color[0] == "#f1c232" ? [true, ss.getRange('K:K').getValues().map(x => { return x != '' && x.toString().match(/TMO/gm) })[index]].flat() : false }) } catch { }
//This will return rows that are not equal to #f1c232 and "TMO"
console.log(res.map((x,index) => {return x[0] == true && x[1] == "TMO" ? ss.hideRows(index+1) : null}))
}
Let me know if this helps.

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.

How to have only one box checked instead of two on Google Apps Script?

function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[1];
if (sheet.getActiveCell() = "E11"){
sheet.getRange('E12').activate();
sheet.getCurrentCell().setValue('FALSE');
sheet.getRange('E13').activate();
sheet.getCurrentCell().setValue('FALSE');
sheet.getRange('E14').activate();
sheet.getCurrentCell().setValue('FALSE');
}
if (sheet.getActiveCell() = "E12"){
sheet.getRange('E11').activate();
sheet.getCurrentCell().setValue('FALSE');
sheet.getRange('E13').activate();
sheet.getCurrentCell().setValue('FALSE');
sheet.getRange('E14').activate();
sheet.getCurrentCell().setValue('FALSE');
}
}
So I want to make it so that if a TRUE statement is inputted, the other checkbox will be FALSE and viceversa.
The official GAS documentation contains lots of useful examples, so please refer to it whenever you are stuck. The example below might help you. This is for the scenario where both checkboxes are directly below each other (rows 1 & 2) in column 1.
function onEdit(e){
//Checkbox coordinates.
var checkboxColumn = 1;
var checkboxRows = [1, 2];
var sheetName = "YOUR_SHEET_NAME";
//Get the edited cell value
var value = e.value;
//Get the old value
var oldValue = e.oldValue;
var editedRange = e.range;
var editedSheet = editedRange.getSheet();
if (editedSheet.getName() == sheetName && editedRange.getColumn() == checkboxColumn && checkboxRows.indexOf(editedRange.getRow()) != -1) {
//Get the row coordinate of the other checkbox
var checkboxRow = checkboxRows.filter(function(rowNum) { return rowNum != editedRange.getRow();})[0];
//set its value to the old value of the edited checkbox
var range = editedSheet.getRange(checkboxRow, checkboxColumn).setValue(oldValue);
}
}
Here's an alternative implementation of #Anton's answer that uses the RangeList class to support arbitrarily oriented "radio button"-grouped checkboxes, provided they're all on the same worksheet (the RangeList range sources must be on the same worksheet).
function onEdit(e) {
const sheetName = 'some sheet name',
rbLocations = [
{r: 1, c: 1}, // A1
{r: 2, c: 4}, // D2
{c: 1, r: 8}, // A8
...
];
if (!e) return; // only run for sheet edits.
const toggle = e.oldValue;
if (toggle === undefined)
return; // require edit to be of a single cell.
const sheet = edited.getSheet();
if (sheet.getName() !== sheetName)
return; // require edit to be of the desired sheet.
const edited = e.range,
eRow = edited.getRow(),
eCol = edited.getColumn(),
otherRBs = rbLocations.filter(
function (loc) {
return loc.r !== eRow || loc.c !== eCol;
}
).map(
function (l) {
// Create an R1C1 notation string.
return "R" + l.r + "C" + l.c;
}
);
// Acquire and use a RangeList to set the same value to all the other checkboxes.
sheet.getRangeList(otherRBs).setValue(toggle);
}
References
Array#filter
Array#map
Sheet#getRangeList
Event objects - edit

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.