Google Sheets Script: Get A1Notation For Each Cell In Named Range - google-apps-script

The function I'm working on does an optimisation of the force required by a rudder foil (stabilator) on an America's Cup AC75 yacht, to balance the longitudinal moments of the sail forces. This is done by altering the angle of the stabilator, from initially providing an upward (positive) force, then as sail forces increase, the stabilator has to create a downward (negative) force.
The stabilator angles are in a column, and when it is changed, other calculations work out if it balances the sail forces. If it doesn't, there is a "Delta" column that indicates a value whether the rudder foil needs to provide more/less force, via it's angle.
I tried using Named Ranges for the column of Angles, and another for Delta. The code should iterate through adding a bit more (or less) angle to the stabilator, and each time, check the Delta. My code is wrong.
What I need to do is get the Angle value of one Angle cell, incrementally increase/decrease it, then setValue of that cell. Next is to getValue of the corresponding Delta cell, to see if I'm at Zero (plus/minus a small amount). If not, via a while loop, I increase/decrease the Angle again, setValue, recheck the Delta, and so on.
My problem is that I do not know how to get the A1Notation of each cell in the Named Ranges as I iterate through it, so that I can repeatedly getValue and setValue for just the single cell at a time?
function c_Optimise_Stabilator() {
// Author: Max Hugen
// Date: 20102-12-07
// Purpose: Attempt to optimise Stab Angle to balance with Stab Target Force
/* WARNING: This function may eventually cause a circular reference, so ensure there is an "escape".
* May occur if other optimisation functions are also run?
* OPTIMISATION: Try in this order:
* 1. Optimise Transverse Moments
* 2. Optimise Stabilator
* 3. Check, and if necessary, rerun Optimise Transverse Moments
* 4. Check, and if necessary, rerun Optimise Stabilator
* If Optimise Stabilator returns all Angles OK, we're good!
*/
const ui = SpreadsheetApp.getUi();
const ss = SpreadsheetApp.getActiveSpreadsheet();
// var target_sheet = "Analysis";
// var sheet = ss.getSheetByName("Analysis");
var msg = ""; // gather input for Logger
var s = ""; // short info for testing alerts, then added to msg
var log = true; // whether to output msg to the Logger
// angle range
const maxAngle = 2.0, minAngle = -0.2, incAngle = 0.1;
//limits
var maxLoopIterations=10; // to avoid excessive iterations
var minDelta=0.02; // to limit the minimum size of Delta tested
// counters
var i=0, loopIterations=0;
// Original and New Vals
var originalAngle=0.0, newAngle=0.0, originalDelta=0.0, newDelta=0.0;
// ranges used in getRange - variable here, for testing. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
/*
var sAngle = "Optimise_Stab_Angle";
var sDelta = "Optimise_Stab_Delta";
var sAngle = "Analysis!AF14:AG14"; // 1 rows (but only 2 cols now) - failing
var sDelta = "Analysis!AM14:AM14";
*/
var sAngle = "Analysis!AF10:AG13"; // 4 rows
var sDelta = "Analysis!AM10:AM13";
var rAngle = ss.getRange(sAngle);
var dAngle = rAngle.getValues();
var rDelta = ss.getRange(sDelta);
var dDelta = rDelta.getValues();
originalAngle = Math.round(dAngle[i][1]*1000)/1000;
originalDelta = Math.round(dDelta[i][0]*1000)/1000;
var iLen = rAngle.getNumRows();
for(i=0; i<iLen; i++){
s = "";
newAngle = originalAngle;
s += " Vb: " + dAngle[i][0] + "; Original Angle: " + originalAngle + "; originalDelta: " + originalDelta + "\r\n";
// if stabilator force is below target (negative Delta), increase stab angle unless at maxAngle.
if ( Math.abs(Math.round(dDelta[i][0]*100)/100) > minDelta && originalAngle < maxAngle) {
loopIterations = 1;
while (newAngle <= maxAngle) {
try {
if ( newAngle == maxAngle ) {
s += " MAX.ANGLE: newDelta" + newDelta + "; originalDelta: " + originalDelta;
break;
}
// Have to update the Delta range, to check if Delta still too high/low
var rDelta = ss.getRange(sDelta);
var dDelta = rDelta.getValues();
newDelta = Math.round(dDelta[i][0]*1000)/1000;
if ( Math.abs(Math.round(newDelta*100)/100) < minDelta ) {
s += " COMPLETED: newDelta" + newDelta + "; originalDelta: " + originalDelta;
break;
}
if ( loopIterations > maxLoopIterations ) {
s += " EXCEEDED maxLoopIterations of " + maxLoopIterations;
break;
}
} catch(err) {
Logger.log (c_ErrorToString (err) + "\n" + "Vb: " + dAngle[i][0]);
}
newAngle += incAngle; // for some reason, this may STILL produce a number like 1.400000003 (example only)
newAngle = Math.round(newAngle*1000)/1000;
// set the new angle
dAngle[i][1] = newAngle;
// update the iteration count
loopIterations ++
}
}
// if stabilator force is above target (positive Delta), decrease stab angle unless at minAngle.
else if ( Math.abs(Math.round(dDelta[i][0]*100)/100) > minDelta && originalAngle > minAngle) {
loopIterations = 1;
while (newAngle >= minAngle) {
try {
if ( newAngle == minAngle ) {
s += " MIN.ANGLE: newDelta" + newDelta + "; originalDelta: " + originalDelta;
break;
}
// Have to update the Delta range, to check if Delta still too high/low
var rDelta = ss.getRange(sDelta);
var dDelta = rDelta.getValues();
newDelta = Math.round(dDelta[i][0]*1000)/1000;
if ( Math.abs(Math.round(newDelta*100)/100) < minDelta ) {
s += " COMPLETED: newDelta" + newDelta + "; originalDelta: " + originalDelta;
break;
}
if ( loopIterations > maxLoopIterations ) {
s += " EXCEEDED maxLoopIterations of " + maxLoopIterations;
break;
}
} catch(err) {
Logger.log (c_ErrorToString (err) + "\n" + "Vb: " + dAngle[i][0]);
}
newAngle -= incAngle; // for some reason, this may STILL produce a number like 1.400000003 (example only)
newAngle = Math.round(newAngle*1000)/1000;
// set the new angle
dAngle[i][1] = newAngle;
// update the iteration count
loopIterations ++
}
}
msg += s + "\r\n";
}
rAngle.setValues(dAngle);
msg = "c_Optimise_Stabilator \r\n" + msg
Logger.log(msg);
ui.alert(msg);
}

Found the way to get the cell A1Notation, use getCell() on the range, then getA1Notation.
cellA1 = range.getCell(1, 1).getA1Notation();

Related

Google Apps Script (seemingly) hangs (circle icon runs indefinitely) randomly in a loop

I didn't do a great job of explaining the problem. The hanging behavior seems to be related to the use of console logging (with either Logger.log or console.log statements). If I comment out the logging statements, the hanging behavior does not occur. So, if I put in a breakpoint, the looping code will stop at the breakpoint one or a few times after clicking Resume and then just hang without getting back to the breakpoint. The number of successful times hitting the breakpoint before the hanging behavior occurs seems to be random. So, it seems that the problem is related to there being console logging in the loop. If I remove the logging statements, the hanging behavior does not occur. Thanks for any suggestions, here.
I have a loop that hangs in the debugger when a breakpoint{s) are set (but seemingly not when there are no breakpoints set). I've tried putting breakpoints in at various locations and then step over the breakpointed statement and then continue execution. Sometimes (randomly) it just never returns to the breakpoint and just runs forever (until timeout). Here's my code. Can anybody see what's going on. Thanks.
function getHikesDataByMonth() {
var firstHikeMonth = events[1][0].getMonth();
var thisHikeMonth = firstHikeMonth;
//var hikesColumn = firstHikeMonth;
var hikeDate, hikeDay,hikeName, hikeLevel, hikeNameShort;
var hikeMonth, newHikeMonth;
var eventRow = 1;
var hikeRow = 0;
var participantNumber = 0;
var numParticipants;
var hikeData = [];
var hikesColumn = [];
var hikeDate, hikeName, hikeNameShort, hikeLevel;
Logger.log("events.length " + events.length)
while (eventRow < events.length) {
//if (events[eventRow][4].isBlank) { break;}
if ((!events[eventRow][4].includes("hike") && !events[eventRow][4].includes("Hike") && !events[eventRow][4].includes("HIKE")) || events[eventRow][4].includes("CANCEL")) {
eventRow += 1;
continue;
}
// found a hike
console.log("Found Hike: " + events[eventRow][4]);
hikeName = events[eventRow][4];
hikeMonth = events[eventRow][0].getMonth() - firstHikeMonth;
if (hikeMonth > thisHikeMonth - firstHikeMonth) {
newHikeMonth = true;
thisHikeMonth = hikeMonth;
} else {
newHikeMonth = false;
}
hikeLevel = getHikeLevel(hikeName);
hikeNameShort = getHikeNameShort(hikeName);
hikeDate = events[eventRow][0];
hikeDay = weekDayNames[hikeDate.getDay()];
hikeData.push(hikeDay);
hikeData.push(hikeDate.toLocaleDateString('en-US'));
hikeData.push(hikeName)
hikeData.push(hikeNameShort);
hikeData.push(hikeLevel);
hikeData.push("");
hikeData.push("");
//hikeRowArray[0] = hikeRow;
//hikeData[hikeRow] = hikeRow;
console.log("Getting Participants: " + hikeName)
while (events[eventRow][4] == hikeName){
if (events[eventRow][9] != "Paid") {
eventRow += 1;
continue;
}
hikeData.push(events[eventRow][6] + " " + events[eventRow][5]);
eventRow += 1;
participantNumber += 1;
}
numParticipants = participantNumber
for (var i = participantNumber+1; i < 30; i++){
hikeData.push("");
}
//Logger.log(hikeParticipants[participantNumber]);
hikeData.push(numParticipants < 15 ? "Yes" : "No");
hikeData.push(15 - numParticipants);
// That's all for that hike - go to next hike
participantNumber = 0;
eventRow += 1;
hikeData.push(hikeData);
hikesColumn.push(hikeData);
console.log("Going to next hike at eventRow:" + eventRow)
if (newHikeMonth) {
hikesDataByMonth.push(hikesColumn);
}
}
}

Create one time popup with materialize css

Is there any way to evade jquery and make notification be shown only one time per browser ?
For example, goes to website, notification pops up and that is it, next time when user comes to site from same browser notification wont be showen to him.
I would mainly try to evade adding jquery just for that, so if anyone knows a way to do this with materializecss or some plain html i would be thankful.
How do you trigger the notification?
You could do a basic localStorage check for example to "detect" if the notification has been displayed or not:
function foo() {
const hasSeenNotification = window.localStorage.getItem('shown');
if (!hasSeenNotification) {
window.localStorage.setItem('shown', true);
// show notification here
// ...
}
}
You need to add cookies.
And then check is it is exists:
if (GetCookieShowMessageDocument('ShowPoPUP'))
{
...
}
Here is a sample:
function GetCookieShowMessageDocument(c_name) {
var i, x, y, ARRcookies = document.cookie.split(";");
for (i = 0; i < ARRcookies.length; i++) {
x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
x = x.replace(/^\s+|\s+$/g, "");
if (x == c_name) {
return unescape(y);
}
}
}
function SetCookieShowMessageDocument(name, value, days) {
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else var expires = "";
document.cookie = name + "=" + escape(value) + expires + "; path=/";
}

Problem with executing google app script (google slides) after renaming presentation and script

I'm quite new to programming and using google apps scripts.
I wrote script that splits selected text in text box (google slides) in different text boxes (each line of initial textbox is a separated textbox). Code was just a modification of examples from developers.google.com.
function SelectedTextGrabber() {
var selection = SlidesApp.getActivePresentation().getSelection();
var selectionType = selection.getSelectionType();
var currentPage;
switch (selectionType) {
case SlidesApp.SelectionType.NONE:
Logger.log('Nothing selected');
break;
...
case SlidesApp.SelectionType.TEXT:
var tableCellRange = selection.getTableCellRange();
if (tableCellRange != null) {
var tableCell = tableCellRange.getTableCells()[0];
Logger.log('Selected text is in a table at row ' +
tableCell.getRowIndex() + ', column ' +
tableCell.getColumnIndex());
}
var textRange = selection.getTextRange();
if (textRange.getStartIndex() == textRange.getEndIndex()) {
Logger.log('Text cursor position: ' + textRange.getStartIndex());
} else {
Logger.log('Selection is a text range from: ' + textRange.getStartIndex() + ' to: ' +
textRange.getEndIndex() + ' is selected');
var s1 = textRange.asString();
var s2 = '';
var s3 = [];
for (var i = 0; i < s1.length; i++){
if (s1[i] === '\n' || i === s1.length -1) {
s3.push(s2);
s2='';
} else {
s2 += s1[i];
}
}
// textbox parameteres
var h4 = 0;
var left = 10;
var top = 10;
var textsize = 12;
var standnum = 37;
var width = 2 * textsize + (textsize - textsize % 2) / 2 * standnum;
Logger.log(width);
var slide = SlidesApp.getActivePresentation().getSlides()[1];
for (var i = 0; i < s3.length; i++){
//анализ размера текстового блока
var s4 = s3[i].length;
if (s4 <= standnum) {
h4 = textsize * 2;
} else {
h4 = textsize * 2 + (s4 - s4 % standnum) / standnum * textsize;
}
var shape = slide.insertShape(SlidesApp.ShapeType.TEXT_BOX, left, top, width, h4);
var textRange = shape.getText();
textRange.setText(s3[i]);
textRange.getTextStyle().setFontSize(textsize);
top += h4;
if (top > 370) {
top = 10;
left += width;
}
}
}
break;
case SlidesApp.SelectionType.TABLE_CELL:
var tableCells = selection.getTableCellRange().getTableCells();
var table = tableCells[0].getParentTable();
Logger.log('There are ' + tableCells.length + ' table cells selected.');
break;
case SlidesApp.SelectionType.PAGE:
var pages = selection.getPageRange().getPages();
Logger.log('There are ' + pages.length + ' pages selected.');
break;
default:
break;
}
}
It worked just fine, but when I renamed script and presentation, I get the TypeError: Cannot call method "getSelectionType" of null. (line 4, file "Code").
After 30 minutes of waiting this script started working again without errors.
I thought it may happen because it takes time to make some changes in google servers.
But when I modified initial text in text box to be splitted the script gave me the same result as I didn't change initial text (the result is separated lines in textboxes but for initial text).
Do u have any idea what should I do to fix it?

AS3 Switch conditions being ignored

So recently I've been working on a game in Flash, using the program Flash Builder and the Flixel engine. I hit a strange snag though, one of the first things that runs is a parser I made to create the screen for each level based on a 2D array in a .txt file, and for some reason, the final letter in each line is being ignored in the switch statement that decides what that index stands for.
This is the part I'm having issues with (what happens in each condition is irrelevant, I just left it in for context, but they all behave correctly):
var row:int;
var column:int;
var currentColor:int = 0;
for (row = 0; row < m_levelData.length; row++) {
for (column = 0; column < m_levelData[0].length; column++) {
var offset:FlxPoint = new FlxPoint(0, 0);
var thisIndex:String = m_levelData[row][column];
//trace("This Line is " + m_levelData[row]);
trace("Current index is " + thisIndex);
//trace(row + " " + column);
switch(thisIndex) {
case Statics.GRID_BIN:
offset.y = (spaceSize.y / Statics.SPRITE_SCALE - BinFront.SIZE.y / Statics.SPRITE_SCALE) / 2;
offset.x = (spaceSize.x/Statics.SPRITE_SCALE - BinFront.SIZE.x/Statics.SPRITE_SCALE) / 2;
var color:uint;
if (m_colors.length < currentColor) {
color = Statics.COLOR_BLACK;
trace("No color found");
} else {
color = m_colors[currentColor];
currentColor++;
trace("Color is " + color);
}
makeBin(spaceSize.x * column + offset.x + levelOffset.x, spaceSize.y * row + offset.y + levelOffset.y, color);
break;
case Statics.GRID_BUILDER:
offset.y = (spaceSize.y/Statics.SPRITE_SCALE - BuilderMain.SIZE.y/Statics.SPRITE_SCALE)/2
offset.x = (spaceSize.x/Statics.SPRITE_SCALE - BuilderMain.SIZE.x/Statics.SPRITE_SCALE) / 2;
makeBuilder(spaceSize.x * column + offset.x + levelOffset.x, spaceSize.y * row + offset.y + levelOffset.y);
break;
case Statics.GRID_CONVEYOR:
var length:int = 0;
if (column == 0 || m_levelData[row][column - 1] != Statics.GRID_CONVEYOR) {
for (i = column; i < m_levelData[row].length; i++) {
if (m_levelData[row][i] == Statics.GRID_CONVEYOR) {
length++;
} else {
break;
}
}
offset.y = (spaceSize.y/Statics.SPRITE_SCALE - Statics.GRID_SIZE.y/Statics.SPRITE_SCALE)/2
offset.x = (spaceSize.x/Statics.SPRITE_SCALE - Statics.GRID_SIZE.x/Statics.SPRITE_SCALE) / 2;
makeConveyor(spaceSize.x * column + offset.x + levelOffset.x, spaceSize.y * row + offset.y + levelOffset.y, length);
}
break;
default:
trace("Nothing at this index, it was " + thisIndex);
}
Statics.GRID_BIN, Statics.GRID_CONVEYOR, and Statics.GRID_BUILDER are all constant strings ("a", "b", and "c" respectively), and I know that this all should be working because it was working before I switched to the parser. Now, your immediate response to that is that the problem is my parser, but I have suspicions that something is a little screwy other than that. Before the switch, I print the value of thisIndex..(trace("Current index is " + thisIndex);), and whenever it is the last letter in a line that was parsed (using split(","), even when it matches one of the switch conditions, the default condition is run and nothing happens.
Has anyone else seen something like this, or am I just making a really dumb mistake?
When you parse and compare strings loaded from a .txt you have to deal with various "hidden" characters. At line endings you might run into CR (\r) LF(\n) or both or the other way round.
It can get a bit fiddly to find out which applies to your system or .txt.
In the end you could probably make it work using String.replace() .
This has been discussed for example here: How do I detect and remove "\n" from given string in action script?
Apart from this it's hard to judge whether there's something wrong with your parser.
Your use of the switch{}-statement itself looks fine and should be working.
If you need to debug the code try feeding it with a hardcoded const :String instead of the .txt-file. Then set a breakpoint (in Flash Builder by double-clicking the line number next to the switch-statemen) and step through it line by line.

getRange(),setFormulas doesn't want to work

I have the following code and when I run it I get the right number of items in sheetFormulas (4), and the array values look correctly formed.
However, I get an error right after the sheetFormulas Browser.msgBox pops up, indicating the getRange().setFormulas line has an issue, but I can't see what it is.
function test(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var testingTarget = ss.getSheetByName("Testing");
var sheetFormulas = new Array();
for (l=0;l<10;l++) {
sheetRowCount = l + 2;
var monthlyTotalCompanyCosts = '=F' + sheetRowCount + '/$F$29*$I$14';
var monthlyTotalCompanyCostsPerEa = '=IFERROR(H' + sheetRowCount + '/C' + sheetRowCount + ')';
var monthlyMargin = '=D' + sheetRowCount + '-F' + sheetRowCount;
var monthlyMarginPctg = '=IFERROR(J' + sheetRowCount + '/D' + sheetRowCount + ')';
sheetFormulas.push(monthlyTotalCompanyCosts,monthlyTotalCompanyCostsPerEa,monthlyMargin,monthlyMarginPctg);
Browser.msgBox("sheetFormulas.length is: " + sheetFormulas.length);
Browser.msgBox("sheetFormulas is: " + sheetFormulas);
testingTarget.getRange(sheetRowCount,8,1,4).setFormulas(sheetFormulas);
sheetFormulas = [];
}
}
Any help is appreciated,
Phil
First, sheetFormulas has to be a 2D array. So try doing
/* Notice the [] around sheetFormulas */
testingTarget.getRange(sheetRowCount,8,1,4).setFormulas([sheetFormulas]);
If you still see other problems, then put your code inside a try{}catch{} block and print out the exception in the catch block.
I also suggest that you print out the formula using Logger.log() before setting it on the spreadsheet.