Ticker labels after plot - label

I would really appreciate some help adding a ticker label after my plot from the code below. Below is an extract of the main code - I've only used the lines for 1 ticker but in reality, I'll have 32 symbols in total so I've omitted the unnecessary code duplication for the other tickers for the purposes of this query.
Any improvements on the rest of the code would also be appreciated. I'm using ticker.new as 1) I don't need the inputs and the plot only seemed to display properly when the session was listed as extended (even though the chart was already set to extended)
//#version=5
indicator('NASDAQ Trend', overlay=true)
// SYMBOLS //
s01 = ticker.new("NASDAQ", "AAL", session.extended)
// CALCULATIONS //
screener_func() =>
//STACKED EMAs
MA1 = ta.ema(close, 5)
MA2 = ta.ema(close, 8)
MA3 = ta.ema(close, 13)
MA4 = ta.ema(close, 21)
MA5 = ta.ema(close, 34)
MA_Stack_Up = (MA1 > MA2) and (MA2 > MA3) and (MA3 > MA4) and (MA4 > MA5)
//CONDITIONS
Uptrend = MA_Stack_Up
Reversal = ((MA1 < MA2) and (MA2 > MA3)) or ((MA1 > MA2) and (MA2 < MA3))
//COLOR CODING
Bar_Color = Uptrend ? color.new(color.green, 25) : Reversal ? color.new(color.yellow, 25) : color.new(color.red, 25)
[Bar_Color]
// Security call
[TS01]= request.security(s01, timeframe.period, screener_func())
// PLOTS //
l_width = 3
shape = plot.style_circles
plot(1, color=TS01, style=shape, linewidth=l_width)
//LABELS//
L1= label.new(bar_index, 1, text=s01, style=label.style_none, textcolor=color.new(color.white, 0), size=size.small)
label.delete(L1[1])
My problem is the resulting label is ={"session":"extended","symbol":"NASDAQ:AAL"}. Ideally, the label should be just AAL

You can write a function that extracts the part you are interested from that string. If your string ALWAYS has the same format ={"session":"extended","symbol":"NASDAQ:AAL"}.
Step 1: Split the string using : as a delimeter. You will then have 4 sub strings.
={"session"
"extended","symbol"
"NASDAQ
AAL"} <-- This is what you want (index: 3)
Step 2: Remove the last two chars. Since the last two chars will always be "}, return a string until last two chars.
getName(_str) =>
string[] _pair = str.split(_str, ":")
string[] _chars = str.split(array.get(_pair, 3), "") // Index 3
int _len = array.size(_chars) - 2 // Don't get the last two chars
string[] _substr = array.new_string(0)
_substr := array.slice(_chars, 0, _len)
string _return = array.join(_substr, "")
Then call this function when you create a label:
//LABELS//
L1= label.new(bar_index, 1, text=getName(s01), style=label.style_none, textcolor=color.new(color.white, 0), size=size.small)
label.delete(L1[1])

No sense in doing all those string gymnastics when the ticker is available. Try this:
//#version=5
indicator('NASDAQ Trend', overlay=true)
// CALCULATIONS // {
// hey look – it's now foldable....
screener_func() =>
// STACKED EMAs
MA1 = ta.ema(close, 5)
MA2 = ta.ema(close, 8)
MA3 = ta.ema(close, 13)
MA4 = ta.ema(close, 21)
MA5 = ta.ema(close, 34)
// CONDITIONS - no sense in allocating extra variables when hidden in function
Uptrend = (MA1 > MA2) and (MA2 > MA3) and (MA3 > MA4) and (MA4 > MA5)
Reversal = ((MA1 < MA2) and (MA2 > MA3)) or ((MA1 > MA2) and (MA2 < MA3))
// DETERMINE COLOR CODING - just return the results
// vs assigning to a variable and then return the variable...
[Uptrend ? color.new(color.green, 25) : Reversal ? color.new(color.yellow, 25) : color.new(color.red, 25)]
// }
// Define tickers and set up labels on last bar
// Note: request security limits the max number of tickers to 40
var tickers = array.from("AA","AAL","AAPL")
// use the index of the symbol in the ticker array as price level to plot...
if barstate.islast
for item in tickers
label.new(bar_index+ 5, array.indexof(tickers,item), item, style=label.style_none, textcolor=color.new(color.white, 0), size=size.small)
// Since the ticker symbol must be know at complile time aka simple string,
// we have to call each symbol separately - what a pain...
// style variations here in case we want to change
var aStyle = plot.style_circles
var aLinewidth = 3
// AA <-- Yep this is definitely not NASDAQ material...
temp1 = ticker.new("NYSE", "AA", session.extended)
[temp2] = request.security(temp1, timeframe.period, screener_func())
plot(0, color= temp2, style=aStyle, linewidth=aLinewidth)
// AAL
temp3 = ticker.new("NASDAQ", "AAL", session.extended)
[temp4] = request.security(temp3, timeframe.period, screener_func())
plot(1, color= temp4, style=aStyle, linewidth=aLinewidth)
// AAPL
temp5 = ticker.new("NASDAQ", "AAPL", session.extended)
[temp6] = request.security(temp5, timeframe.period, screener_func())
plot(2, color= temp6, style=aStyle, linewidth=aLinewidth)
output image

Related

Google Script - Macro to get data from a TABLE A and paste required values to Table B in sequence using LOOP

I seek help in finalizing a MACRO script for following task.
Basic requirements is to get data from 'Ref' tab (around 200 symbols) in a sequence to 'Mcontrol'!E3 (this tab imports a table from the Website)
From this table, sorted highest 2 values of Symbol/s is extracted and exported to 'OI EOD' tab,
i.e. required value is shown in 'OI EOD'! G5:K5.
Then, copy paste range 'OI EOD'! G5:K5 , to the matched row (symbol match to 'Mcontrol'!E3) in the table below ('OI EOD'!C6:C210).
I seek help in putting forEach condition in the macro. SO that next cycle of copy paste function is completed, for each new value in Cell_1 reference.
Hope ppl take notice of the help request from a novice and give good solution and guidance.
Sheet link is Following
https://docs.google.com/spreadsheets/d/1az2kas91KFxHcWhtWDg-g20hNYR-_zEiWPvYs-x42YU/edit?usp=sharing
Macro -
var wb = SpreadsheetApp.getActive();
var sh1= wb.getSheetByName('Mcontrol');
var sh2= wb.getSheetByName('OI EOD');
var R1 = 3
var C1 = 3
var R2 = 3
var C2 = 2
var R3 = 3
var C3 = 5
var cell_1 = sh1.getRange(R1,C1).getValue();
var cell_2 = sh1.getRange(R2,C2).getValue();
var cell_3 = sh1.getRange(R3,C3).getValue();
for(i=1;i<cell_2;i++);
cell_1 = i + 1;
sh1.getRange(R1,C1).setValue(cell_1);{
var Range_1 = sh2.getRange(6, 3, 300);
var row_i = 5 ; // source row no. for data copy paste
const Row_1 = row_i;
const Col_2 = 7;
const Row_Offet1 = 1;
const Col_Offet1 = 5;
var data = Range_1.getValues();
let ABC = cell_3;
let row = 1 + data.findIndex(users => {return users[0] == ABC});
var row_target = row + row_i ;
const sourceRange = sh2.getRange(Row_1,Col_2,Row_Offet1,Col_Offet1);
var destRange = sh2.getRange(row_target,Col_2,Row_Offet1,Col_Offet1);
sourceRange.copyTo(destRange,SpreadsheetApp.CopyPasteType.PASTE_VALUES,false);
for(k=1;k<cell_1.i;k++);
Logger.log(row_target);
Logger.log(k) ;
}
//NEXT
// cell_1=sh1.getRange(R1, C1);
// sh1.getRange(R1,C1).setValue(i);
}
}```
Thanks
Robin
Not sure if understand the task as a whole, so here is a guess.
This way you can get values from some start to some end 'symbols' (or numbers) and put these values in correct rows on the 'OI EOD' sheet:
function main() {
var start = 1;
var end = 5;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mcontrol_sheet = ss.getSheetByName('Mcontrol');
var oi_eod_sheet = ss.getSheetByName('OI EOD');
// get a list of all Symbols from column 'C' of the sheet 'OI EOD'
var symbols = oi_eod_sheet.getRange('c6:c').getValues().flat();
for (let i = start; i <= end; i++) {
// set the number i on the sheet 'Mcontrol' into the cell 'C3'
mcontrol_sheet.getRange('c3').setValue(i);
// wait to complete all the changes on the spreadsheet
SpreadsheetApp.flush();
// get the Symbol and Values from the range 'C5:K5' of the sheet 'OI EOD'
var [symbl, _, _, _, ...values] = oi_eod_sheet.getRange('c5:k5').getValues()[0];
// get the row index for this Symbol on the sheet 'Mcontrol'
var row = symbols.indexOf(symbl) + 6;
console.log({row}, {symbl}, {values});
// set the values on the sheet 'OI EOD' on the row with given index
oi_eod_sheet.getRange('g' + row + ':k' + row).setValues([values]);
}
}
It's up to you to decide how the start and end values could be defined. It can be values from two cells. Or it can be selected range. Or something else.

Pinescript //#version=5, trying to combine 2 EMAs and PSAR and add buy/sell signals

im really new at coding, so i know it doesnt run, but this what i got.
I want to show buy signal when PSAR goes below the chart and the EMAs are below the chart(EMA200 below EMA20)
I want to show sellsignal when PSAR goes above the chart and the EMAs are above the chart(EMA200 above EMA20)
I find it really good automated startegy with R.R=2 .
if you can help me run the code i reall appreciate your time and effort.
my effort is below - THANKS AGAIN :
strategy("PSAR 2EMAs Strategy",shorttitle="PSAR Buy/Sell" ,overlay=true )
indicator(title="Parabolic SAR", shorttitle="SAR", overlay=true, timeframe="",
timeframe_gaps=true)
start = input(0.02)
increment = input(0.02)
maximum = input(0.2, "Max Value")
out3 = ta.sar(start, increment, maximum)
plot(out3, "ParabolicSAR", style=plot.style_cross, color=#2962FF)
indicator(title="Moving Average Exponential", shorttitle="EMA1", overlay=true,
timeframe="", timeframe_gaps=true)
len = input.int(20, minval=1, title="Length")
src = input(close, title="Source")
offset = input.int(title="Offset", defval=0, minval=-500, maxval=500)
out = ta.ema(src, len)
plot(out, title="EMA", color=color.yellow, offset=offset)
ma(source, length, type) =>
switch type
"SMA" => ta.sma(source, length)
"EMA" => ta.ema(source, length)
"SMMA (RMA)" => ta.rma(source, length)
"WMA" => ta.wma(source, length)
"VWMA" => ta.vwma(source, length)
typeMA = input.string(title = "Method", defval = "SMA", options=["SMA", "EMA", "SMMA
(RMA)", "WMA", "VWMA"], group="Smoothing")
smoothingLength = input.int(title = "Length", defval = 5, minval = 1, maxval = 100,
group="Smoothing")
smoothingLine = ma(out, smoothingLength, typeMA)
plot(smoothingLine, title="Smoothing Line", color=#f37f20, offset=offset,
display=display.none)
indicator(title="Moving Average Exponential", shorttitle="EMA2", overlay=true,
timeframe="", timeframe_gaps=true)
len2 = input.int(200, minval=1, title="Length")
src2 = input(close, title="Source")
offset2 = input.int(title="Offset", defval=0, minval=-500, maxval=500)
out2 = ta.ema(src, len)
plot(out2, title="EMA", color=color.blue, offset2=offset2)
ma(source, length, type) =>
switch type
"SMA" => ta.sma(source, length)
"EMA" => ta.ema(source, length)
"SMMA (RMA)" => ta.rma(source, length)
"WMA" => ta.wma(source, length)
"VWMA" => ta.vwma(source, length)
typeMA = input.string(title = "Method", defval = "SMA", options=["SMA", "EMA", "SMMA
(RMA)", "WMA", "VWMA"], group="Smoothing")
smoothingLength = input.int(title = "Length", defval = 5, minval = 1, maxval = 100,
group="Smoothing")
smoothingLine = ma(out, smoothingLength, typeMA)
plot(smoothingLine, title="Smoothing Line", color=#f37f20, offset=offset,
display=display.none)
//the strategy goes like this: WHEN PSAR gets below the chart and EMA200 is below EMA20
and chart is above the 2 EMAs: "BUY"
// WHEN PSAR gets above the chart and EMA200 is above EMA20
and chart is below the 2 EMAs: "SELL"
if ta.change("PSAR Buy/Sell") < 0 and EMA1 below EMA2
strategy.entry("My Long Entry Id", strategy.long)
if ta.change("PSAR Buy/Sell") > 0 and EMA1 above EMA2
strategy.entry("My Short Entry Id", strategy.short)

How do you do dynamic / dependent drop downs in Google Sheets?

How do you get a sub-category column to populate a drop down based on the value selected in the main category drop down in google sheets?
I googled around and couldn't find any good solutions, therefore I wanted to share my own. Please see my answer below.
You can start with a google sheet set up with a main page and drop down source page like shown below.
You can set up the first column drop down through the normal Data > Validations menu prompts.
Main Page
Drop Down Source Page
After that, you need to set up a script with the name onEdit. (If you don't use that name, the getActiveRange() will do nothing but return cell A1)
And use the code provided here:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var myRange = SpreadsheetApp.getActiveRange();
var dvSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Categories");
var option = new Array();
var startCol = 0;
if(sheet.getName() == "Front Page" && myRange.getColumn() == 1 && myRange.getRow() > 1){
if(myRange.getValue() == "Category 1"){
startCol = 1;
} else if(myRange.getValue() == "Category 2"){
startCol = 2;
} else if(myRange.getValue() == "Category 3"){
startCol = 3;
} else if(myRange.getValue() == "Category 4"){
startCol = 4;
} else {
startCol = 10
}
if(startCol > 0 && startCol < 10){
option = dvSheet.getSheetValues(3,startCol,10,1);
var dv = SpreadsheetApp.newDataValidation();
dv.setAllowInvalid(false);
//dv.setHelpText("Some help text here");
dv.requireValueInList(option, true);
sheet.getRange(myRange.getRow(),myRange.getColumn() + 1).setDataValidation(dv.build());
}
if(startCol == 10){
sheet.getRange(myRange.getRow(),myRange.getColumn() + 1).clearDataValidations();
}
}
}
After that, set up a trigger in the script editor screen by going to Edit > Current Project Triggers. This will bring up a window to have you select various drop downs to eventually end up at this:
You should be good to go after that!
Caution! The scripts have a limit: it handles up to 500 values in a single drop-down list.
Multi-line, multi-Level, multi-List, multi-Edit-Line Dependent Drop-Down Lists in Google Sheets. Script
More Info
Article
Video
Last version of the script on GitHub
This solution is not perfect, but it gives some benefits:
Let you make multiple dropdown lists
Gives more control
Source Data is placed on the only sheet, so it's simple to edit
First of all, here's working example, so you can test it before going further.
Installation:
Prepare Data
Make the first list as usual: Data > Validation
Add Script, set some variables
Done!
Prepare Data
Data looks like a single table with all possible variants inside it. It must be located on a separate sheet, so it can be used by the script. Look at this example:
Here we have four levels, each value repeats. Note that 2 columns on the right of data are reserved, so don't type/paste there any data.
First simple Data Validation (DV)
Prepare a list of unique values. In our example, it is a list of Planets. Find free space on sheet with data, and paste formula: =unique(A:A)
On your mainsheet select first column, where DV will start. Go to Data > Validation and select range with a unique list.
Script
Paste this code into script editor:
function onEdit(event)
{
// Change Settings:
//--------------------------------------------------------------------------------------
var TargetSheet = 'Main'; // name of sheet with data validation
var LogSheet = 'Data1'; // name of sheet with data
var NumOfLevels = 4; // number of levels of data validation
var lcol = 2; // number of column where validation starts; A = 1, B = 2, etc.
var lrow = 2; // number of row where validation starts
var offsets = [1,1,1,2]; // offsets for levels
// ^ means offset column #4 on one position right.
// =====================================================================================
SmartDataValidation(event, TargetSheet, LogSheet, NumOfLevels, lcol, lrow, offsets);
// Change Settings:
//--------------------------------------------------------------------------------------
var TargetSheet = 'Main'; // name of sheet with data validation
var LogSheet = 'Data2'; // name of sheet with data
var NumOfLevels = 7; // number of levels of data validation
var lcol = 9; // number of column where validation starts; A = 1, B = 2, etc.
var lrow = 2; // number of row where validation starts
var offsets = [1,1,1,1,1,1,1]; // offsets for levels
// =====================================================================================
SmartDataValidation(event, TargetSheet, LogSheet, NumOfLevels, lcol, lrow, offsets);
}
function SmartDataValidation(event, TargetSheet, LogSheet, NumOfLevels, lcol, lrow, offsets)
{
//--------------------------------------------------------------------------------------
// The event handler, adds data validation for the input parameters
//--------------------------------------------------------------------------------------
var FormulaSplitter = ';'; // depends on regional setting, ';' or ',' works for US
//--------------------------------------------------------------------------------------
// =================================== key variables =================================
//
// ss sheet we change (TargetSheet)
// br range to change
// scol number of column to edit
// srow number of row to edit
// CurrentLevel level of drop-down, which we change
// HeadLevel main level
// r current cell, which was changed by user
// X number of levels could be checked on the right
//
// ls Data sheet (LogSheet)
//
// ======================================================================================
// Checks
var ts = event.source.getActiveSheet();
var sname = ts.getName();
if (sname !== TargetSheet) { return -1; } // not main sheet
// Test if range fits
var br = event.range;
var scol = br.getColumn(); // the column number in which the change is made
var srow = br.getRow() // line number in which the change is made
var ColNum = br.getWidth();
if ((scol + ColNum - 1) < lcol) { return -2; } // columns...
if (srow < lrow) { return -3; } // rows
// Test range is in levels
var columnsLevels = getColumnsOffset_(offsets, lcol); // Columns for all levels
var CurrentLevel = getCurrentLevel_(ColNum, br, scol, columnsLevels);
if(CurrentLevel === 1) { return -4; } // out of data validations
if(CurrentLevel > NumOfLevels) { return -5; } // last level
/*
ts - sheet with validation, sname = name of sheet
NumOfLevels = 4
offsets = [1,1,1,2] - last offset is 2 because need to skip 1 column
columnsLevels = [4,5,6,8] - Columns of validation
Columns 7 is skipped
|
1 2 3 4 5 6 7 8 9
|----+----+----+----+----+----+----+----+----+
1 | | | | | | | x | | |
|----+----+----+----+----+----+----+----+----+
2 | | | | v | V | ? | x | ? | | lrow = 2 - number of row where validation starts
|----+----+----+----+----+----+----+----+----+
3 | | | | | | | x | | |
|----+----+----+----+----+----+----+----+----+
4 | | | | | | | x | | |
|----+----+----+----+----+----+----+----+----+
| | | | |
| | | | Currentlevel = 3 - the number of level to change
| | | |
| | | br - cell, user changes: scol - column, srow - row,
| | ColNum = 1 - width
|__|________ _.....____|
| v
| Drop-down lists
|
| lcol = 4 - number of column where validation starts
*/
// Constants
var ReplaceCommas = getDecimalMarkIsCommaLocals(); // // ReplaceCommas = true if locale uses commas to separate decimals
var ls = SpreadsheetApp.getActive().getSheetByName(LogSheet); // Data sheet
var RowNum = br.getHeight();
/* Adjust the range 'br'
??? !
xxx x
xxx x
xxx => x
xxx x
xxx x
*/
br = ts.getRange(br.getRow(), columnsLevels[CurrentLevel - 2], RowNum);
// Levels
var HeadLevel = CurrentLevel - 1; // main level
var X = NumOfLevels - CurrentLevel + 1; // number of levels left
// determine columns on the sheet "Data"
var KudaCol = NumOfLevels + 2;
var KudaNado = ls.getRange(1, KudaCol); // 1 place for a formula
var lastRow = ls.getLastRow();
var ChtoNado = ls.getRange(1, KudaCol, lastRow, KudaCol); // the range with list, returned by a formula
// ============================================================================= > loop >
var CurrLevelBase = CurrentLevel; // remember the first current level
for (var j = 1; j <= RowNum; j++) // [01] loop rows start
{
// refresh first val
var currentRow = br.getCell(j, 1).getRow();
loopColumns_(HeadLevel, X, currentRow, NumOfLevels, CurrLevelBase, lastRow, FormulaSplitter, CurrLevelBase, columnsLevels, br, KudaNado, ChtoNado, ReplaceCommas, ts);
} // [01] loop rows end
}
function getColumnsOffset_(offsets, lefColumn)
{
// Columns for all levels
var columnsLevels = [];
var totalOffset = 0;
for (var i = 0, l = offsets.length; i < l; i++)
{
totalOffset += offsets[i];
columnsLevels.push(totalOffset + lefColumn - 1);
}
return columnsLevels;
}
function test_getCurrentLevel()
{
var br = SpreadsheetApp.getActive().getActiveSheet().getRange('A5:C5');
var scol = 1;
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxx|
dv range |xxxxxxxxxxxxxxxxx|
levels 1 2 3
level 2
*/
Logger.log(getCurrentLevel_(1, br, scol, [1,2,3])); // 2
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxx|
dv range |xxxxx| |xxxxx| |xxxxx|
levels 1 2 3
level 2
*/
Logger.log(getCurrentLevel_(2, br, scol, [1,3,5])); // 2
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxxxxxxxx|
dv range |xxxxx| |xxxxxxxxxxx|
levels 1 2 3
level 2
*/
Logger.log(getCurrentLevel_(3, br, scol, [1,5,6])); // 2
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxxxxxxxx|
dv range |xxxxxxxxxxx| |xxxxx|
levels 1 2 3
level 3
*/
Logger.log(getCurrentLevel_(3, br, scol, [1,2,8])); // 3
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxxxxxxxx|
dv range |xxxxxxxxxxxxxxxxx|
levels 1 2 3
level 4 (error)
*/
Logger.log(getCurrentLevel_(3, br, scol, [1,2,3]));
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxxxxxxxx|
dv range |xxxxxxxxxxxxxxxxx|
levels
level 1 (error)
*/
Logger.log(getCurrentLevel_(3, br, scol, [5,6,7])); // 1
}
function getCurrentLevel_(ColNum, br, scol, columnsLevels)
{
var colPlus = 2; // const
if (ColNum === 1) { return columnsLevels.indexOf(scol) + colPlus; }
var CurrentLevel = -1;
var level = 0;
var column = 0;
for (var i = 0; i < ColNum; i++ )
{
column = br.offset(0, i).getColumn();
level = columnsLevels.indexOf(column) + colPlus;
if (level > CurrentLevel) { CurrentLevel = level; }
}
return CurrentLevel;
}
function loopColumns_(HeadLevel, X, currentRow, NumOfLevels, CurrentLevel, lastRow, FormulaSplitter, CurrLevelBase, columnsLevels, br, KudaNado, ChtoNado, ReplaceCommas, ts)
{
for (var k = 1; k <= X; k++)
{
HeadLevel = HeadLevel + k - 1;
CurrentLevel = CurrLevelBase + k - 1;
var r = ts.getRange(currentRow, columnsLevels[CurrentLevel - 2]);
var SearchText = r.getValue(); // searched text
X = loopColumn_(X, SearchText, HeadLevel, HeadLevel, currentRow, NumOfLevels, CurrentLevel, lastRow, FormulaSplitter, CurrLevelBase, columnsLevels, br, KudaNado, ChtoNado, ReplaceCommas, ts);
}
}
function loopColumn_(X, SearchText, HeadLevel, HeadLevel, currentRow, NumOfLevels, CurrentLevel, lastRow, FormulaSplitter, CurrLevelBase, columnsLevels, br, KudaNado, ChtoNado, ReplaceCommas, ts)
{
// if nothing is chosen!
if (SearchText === '') // condition value =''
{
// kill extra data validation if there were
// columns on the right
if (CurrentLevel <= NumOfLevels)
{
for (var f = 0; f < X; f++)
{
var cell = ts.getRange(currentRow, columnsLevels[CurrentLevel + f - 1]);
// clean & get rid of validation
cell.clear({contentsOnly: true});
cell.clear({validationsOnly: true});
// exit columns loop
}
}
return 0; // end loop this row
}
// formula for values
var formula = getDVListFormula_(CurrentLevel, currentRow, columnsLevels, lastRow, ReplaceCommas, FormulaSplitter, ts);
KudaNado.setFormula(formula);
// get response
var Response = getResponse_(ChtoNado, lastRow, ReplaceCommas);
var Variants = Response.length;
// build data validation rule
if (Variants === 0.0) // empty is found
{
return;
}
if(Variants >= 1.0) // if some variants were found
{
var cell = ts.getRange(currentRow, columnsLevels[CurrentLevel - 1]);
var rule = SpreadsheetApp
.newDataValidation()
.requireValueInList(Response, true)
.setAllowInvalid(false)
.build();
// set validation rule
cell.setDataValidation(rule);
}
if (Variants === 1.0) // // set the only value
{
cell.setValue(Response[0]);
SearchText = null;
Response = null;
return X; // continue doing DV
} // the only value
return 0; // end DV in this row
}
function getDVListFormula_(CurrentLevel, currentRow, columnsLevels, lastRow, ReplaceCommas, FormulaSplitter, ts)
{
var checkVals = [];
var Offs = CurrentLevel - 2;
var values = [];
// get values and display values for a formula
for (var s = 0; s <= Offs; s++)
{
var checkR = ts.getRange(currentRow, columnsLevels[s]);
values.push(checkR.getValue());
}
var LookCol = colName(CurrentLevel-1); // gets column name "A,B,C..."
var formula = '=unique(filter(' + LookCol + '2:' + LookCol + lastRow; // =unique(filter(A2:A84
var mathOpPlusVal = '';
var value = '';
// loop levels for multiple conditions
for (var i = 0; i < CurrentLevel - 1; i++) {
formula += FormulaSplitter; // =unique(filter(A2:A84;
LookCol = colName(i);
value = values[i];
mathOpPlusVal = getValueAndMathOpForFunction_(value, FormulaSplitter, ReplaceCommas); // =unique(filter(A2:A84;B2:B84="Text"
if ( Array.isArray(mathOpPlusVal) )
{
formula += mathOpPlusVal[0];
formula += LookCol + '2:' + LookCol + lastRow; // =unique(filter(A2:A84;ROUND(B2:B84
formula += mathOpPlusVal[1];
}
else
{
formula += LookCol + '2:' + LookCol + lastRow; // =unique(filter(A2:A84;B2:B84
formula += mathOpPlusVal;
}
}
formula += "))"; //=unique(filter(A2:A84;B2:B84="Text"))
return formula;
}
function getValueAndMathOpForFunction_(value, FormulaSplitter, ReplaceCommas)
{
var result = '';
var splinter = '';
var type = typeof value;
// strings
if (type === 'string') return '="' + value + '"';
// date
if(value instanceof Date)
{
return ['ROUND(', FormulaSplitter +'5)=ROUND(DATE(' + value.getFullYear() + FormulaSplitter + (value.getMonth() + 1) + FormulaSplitter + value.getDate() + ')' + '+'
+ 'TIME(' + value.getHours() + FormulaSplitter + value.getMinutes() + FormulaSplitter + value.getSeconds() + ')' + FormulaSplitter + '5)'];
}
// numbers
if (type === 'number')
{
if (ReplaceCommas)
{
return '+0=' + value.toString().replace('.', ',');
}
else
{
return '+0=' + value;
}
}
// booleans
if (type === 'boolean')
{
return '=' + value;
}
// other
return '=' + value;
}
function getResponse_(allRange, l, ReplaceCommas)
{
var data = allRange.getValues();
var data_ = allRange.getDisplayValues();
var response = [];
var val = '';
for (var i = 0; i < l; i++)
{
val = data[i][0];
if (val !== '')
{
var type = typeof val;
if (type === 'boolean' || val instanceof Date) val = String(data_[i][0]);
if (type === 'number' && ReplaceCommas) val = val.toString().replace('.', ',')
response.push(val);
}
}
return response;
}
function colName(n) {
var ordA = 'a'.charCodeAt(0);
var ordZ = 'z'.charCodeAt(0);
var len = ordZ - ordA + 1;
var s = "";
while(n >= 0) {
s = String.fromCharCode(n % len + ordA) + s;
n = Math.floor(n / len) - 1;
}
return s;
}
function getDecimalMarkIsCommaLocals() {
// list of Locals Decimal mark = comma
var LANGUAGE_BY_LOCALE = {
af_NA: "Afrikaans (Namibia)",
af_ZA: "Afrikaans (South Africa)",
af: "Afrikaans",
sq_AL: "Albanian (Albania)",
sq: "Albanian",
ar_DZ: "Arabic (Algeria)",
ar_BH: "Arabic (Bahrain)",
ar_EG: "Arabic (Egypt)",
ar_IQ: "Arabic (Iraq)",
ar_JO: "Arabic (Jordan)",
ar_KW: "Arabic (Kuwait)",
ar_LB: "Arabic (Lebanon)",
ar_LY: "Arabic (Libya)",
ar_MA: "Arabic (Morocco)",
ar_OM: "Arabic (Oman)",
ar_QA: "Arabic (Qatar)",
ar_SA: "Arabic (Saudi Arabia)",
ar_SD: "Arabic (Sudan)",
ar_SY: "Arabic (Syria)",
ar_TN: "Arabic (Tunisia)",
ar_AE: "Arabic (United Arab Emirates)",
ar_YE: "Arabic (Yemen)",
ar: "Arabic",
hy_AM: "Armenian (Armenia)",
hy: "Armenian",
eu_ES: "Basque (Spain)",
eu: "Basque",
be_BY: "Belarusian (Belarus)",
be: "Belarusian",
bg_BG: "Bulgarian (Bulgaria)",
bg: "Bulgarian",
ca_ES: "Catalan (Spain)",
ca: "Catalan",
tzm_Latn: "Central Morocco Tamazight (Latin)",
tzm_Latn_MA: "Central Morocco Tamazight (Latin, Morocco)",
tzm: "Central Morocco Tamazight",
da_DK: "Danish (Denmark)",
da: "Danish",
nl_BE: "Dutch (Belgium)",
nl_NL: "Dutch (Netherlands)",
nl: "Dutch",
et_EE: "Estonian (Estonia)",
et: "Estonian",
fi_FI: "Finnish (Finland)",
fi: "Finnish",
fr_BE: "French (Belgium)",
fr_BJ: "French (Benin)",
fr_BF: "French (Burkina Faso)",
fr_BI: "French (Burundi)",
fr_CM: "French (Cameroon)",
fr_CA: "French (Canada)",
fr_CF: "French (Central African Republic)",
fr_TD: "French (Chad)",
fr_KM: "French (Comoros)",
fr_CG: "French (Congo - Brazzaville)",
fr_CD: "French (Congo - Kinshasa)",
fr_CI: "French (Côte d’Ivoire)",
fr_DJ: "French (Djibouti)",
fr_GQ: "French (Equatorial Guinea)",
fr_FR: "French (France)",
fr_GA: "French (Gabon)",
fr_GP: "French (Guadeloupe)",
fr_GN: "French (Guinea)",
fr_LU: "French (Luxembourg)",
fr_MG: "French (Madagascar)",
fr_ML: "French (Mali)",
fr_MQ: "French (Martinique)",
fr_MC: "French (Monaco)",
fr_NE: "French (Niger)",
fr_RW: "French (Rwanda)",
fr_RE: "French (Réunion)",
fr_BL: "French (Saint Barthélemy)",
fr_MF: "French (Saint Martin)",
fr_SN: "French (Senegal)",
fr_CH: "French (Switzerland)",
fr_TG: "French (Togo)",
fr: "French",
gl_ES: "Galician (Spain)",
gl: "Galician",
ka_GE: "Georgian (Georgia)",
ka: "Georgian",
de_AT: "German (Austria)",
de_BE: "German (Belgium)",
de_DE: "German (Germany)",
de_LI: "German (Liechtenstein)",
de_LU: "German (Luxembourg)",
de_CH: "German (Switzerland)",
de: "German",
el_CY: "Greek (Cyprus)",
el_GR: "Greek (Greece)",
el: "Greek",
hu_HU: "Hungarian (Hungary)",
hu: "Hungarian",
is_IS: "Icelandic (Iceland)",
is: "Icelandic",
id_ID: "Indonesian (Indonesia)",
id: "Indonesian",
it_IT: "Italian (Italy)",
it_CH: "Italian (Switzerland)",
it: "Italian",
kab_DZ: "Kabyle (Algeria)",
kab: "Kabyle",
kl_GL: "Kalaallisut (Greenland)",
kl: "Kalaallisut",
lv_LV: "Latvian (Latvia)",
lv: "Latvian",
lt_LT: "Lithuanian (Lithuania)",
lt: "Lithuanian",
mk_MK: "Macedonian (Macedonia)",
mk: "Macedonian",
naq_NA: "Nama (Namibia)",
naq: "Nama",
pl_PL: "Polish (Poland)",
pl: "Polish",
pt_BR: "Portuguese (Brazil)",
pt_GW: "Portuguese (Guinea-Bissau)",
pt_MZ: "Portuguese (Mozambique)",
pt_PT: "Portuguese (Portugal)",
pt: "Portuguese",
ro_MD: "Romanian (Moldova)",
ro_RO: "Romanian (Romania)",
ro: "Romanian",
ru_MD: "Russian (Moldova)",
ru_RU: "Russian (Russia)",
ru_UA: "Russian (Ukraine)",
ru: "Russian",
seh_MZ: "Sena (Mozambique)",
seh: "Sena",
sk_SK: "Slovak (Slovakia)",
sk: "Slovak",
sl_SI: "Slovenian (Slovenia)",
sl: "Slovenian",
es_AR: "Spanish (Argentina)",
es_BO: "Spanish (Bolivia)",
es_CL: "Spanish (Chile)",
es_CO: "Spanish (Colombia)",
es_CR: "Spanish (Costa Rica)",
es_DO: "Spanish (Dominican Republic)",
es_EC: "Spanish (Ecuador)",
es_SV: "Spanish (El Salvador)",
es_GQ: "Spanish (Equatorial Guinea)",
es_GT: "Spanish (Guatemala)",
es_HN: "Spanish (Honduras)",
es_419: "Spanish (Latin America)",
es_MX: "Spanish (Mexico)",
es_NI: "Spanish (Nicaragua)",
es_PA: "Spanish (Panama)",
es_PY: "Spanish (Paraguay)",
es_PE: "Spanish (Peru)",
es_PR: "Spanish (Puerto Rico)",
es_ES: "Spanish (Spain)",
es_US: "Spanish (United States)",
es_UY: "Spanish (Uruguay)",
es_VE: "Spanish (Venezuela)",
es: "Spanish",
sv_FI: "Swedish (Finland)",
sv_SE: "Swedish (Sweden)",
sv: "Swedish",
tr_TR: "Turkish (Turkey)",
tr: "Turkish",
uk_UA: "Ukrainian (Ukraine)",
uk: "Ukrainian",
vi_VN: "Vietnamese (Vietnam)",
vi: "Vietnamese"
}
var SS = SpreadsheetApp.getActiveSpreadsheet();
var LocalS = SS.getSpreadsheetLocale();
if (LANGUAGE_BY_LOCALE[LocalS] == undefined) {
return false;
}
//Logger.log(true);
return true;
}
/*
function ReplaceDotsToCommas(dataIn) {
var dataOut = dataIn.map(function(num) {
if (isNaN(num)) {
return num;
}
num = num.toString();
return num.replace(".", ",");
});
return dataOut;
}
*/
Here's set of variables that are to be changed, you'll find them in script:
var TargetSheet = 'Main'; // name of sheet with data validation
var LogSheet = 'Data2'; // name of sheet with data
var NumOfLevels = 7; // number of levels of data validation
var lcol = 9; // number of column where validation starts; A = 1, B = 2, etc.
var lrow = 2; // number of row where validation starts
var offsets = [1,1,1,1,1,1,1]; // offsets for levels
I suggest everyone, who knows scripts well, send your edits to this code. I guess, there's simpler way to find validation list and make script run faster.
Here you have another solution based on the one provided by #tarheel
function onEdit() {
var sheetWithNestedSelectsName = "Sitemap";
var columnWithNestedSelectsRoot = 1;
var sheetWithOptionPossibleValuesSuffix = "TabSections";
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = SpreadsheetApp.getActiveSheet();
// If we're not in the sheet with nested selects, exit!
if ( activeSheet.getName() != sheetWithNestedSelectsName ) {
return;
}
var activeCell = SpreadsheetApp.getActiveRange();
// If we're not in the root column or a content row, exit!
if ( activeCell.getColumn() != columnWithNestedSelectsRoot || activeCell.getRow() < 2 ) {
return;
}
var sheetWithActiveOptionPossibleValues = activeSpreadsheet.getSheetByName( activeCell.getValue() + sheetWithOptionPossibleValuesSuffix );
// Get all possible values
var activeOptionPossibleValues = sheetWithActiveOptionPossibleValues.getSheetValues( 1, 1, -1, 1 );
var possibleValuesValidation = SpreadsheetApp.newDataValidation();
possibleValuesValidation.setAllowInvalid( false );
possibleValuesValidation.requireValueInList( activeOptionPossibleValues, true );
activeSheet.getRange( activeCell.getRow(), activeCell.getColumn() + 1 ).setDataValidation( possibleValuesValidation.build() );
}
It has some benefits over the other approach:
You don't need to edit the script every time you add a "root option". You only have to create a new sheet with the nested options of this root option.
I've refactored the script providing more semantic names for the variables and so on. Furthermore, I've extracted some parameters to variables in order to make it easier to adapt to your specific case. You only have to set the first 3 values.
There's no limit of nested option values (I've used the getSheetValues method with the -1 value).
So, how to use it:
Create the sheet where you'll have the nested selectors
Go to the "Tools" > "Script Editor…" and select the "Blank project" option
Paste the code attached to this answer
Modify the first 3 variables of the script setting up your values and save it
Create one sheet within this same document for each possible value of the "root selector". They must be named as the value + the specified suffix.
Enjoy!
Edit: The answer below may be satisfactory, but it has some drawbacks:
There is a noticeable pause for the running of the script. I'm on a 160 ms latency, and it's enough to be annoying.
It works by building a new range each time you edit a given row. This gives an 'invalid contents' to previous entries some of the time
I hope others can clean this up somewhat.
Here's another way to do it, that saves you a ton of range naming:
Three sheets in the worksheet: call them Main, List, and DRange (for dynamic range.)
On the Main sheet, column 1 contains a timestamp. This time stamp is modified onEdit.
On List your categories and subcategories are arranged as a simple list. I'm using this for plant inventory at my tree farm, so my list looks like this:
Group | Genus | Bot_Name
Conifer | Abies | Abies balsamea
Conifer | Abies | Abies concolor
Conifer | Abies | Abies lasiocarpa var bifolia
Conifer | Pinus | Pinus ponderosa
Conifer | Pinus | Pinus sylvestris
Conifer | Pinus | Pinus banksiana
Conifer | Pinus | Pinus cembra
Conifer | Picea | Picea pungens
Conifer | Picea | Picea glauca
Deciduous | Acer | Acer ginnala
Deciduous | Acer | Acer negundo
Deciduous | Salix | Salix discolor
Deciduous | Salix | Salix fragilis
...
Where | indicates separation into columns.
For convenience I also used the headers as names for named ranges.
DRrange A1 has the formula
=Max(Main!A2:A1000)
This returns the most recent timestamp.
A2 to A4 have variations on:
=vlookup($A$1,Inventory!$A$1:$E$1000,2,False)
with the 2 being incremented for each cell to the right.
On running A2 to A4 will have the currently selected Group, Genus and Species.
Below each of these, is a filter command something like this:
=unique(filter(Bot_Name,REGEXMATCH(Bot_Name,C1)))
These filters will populate a block below with matching entries to the contents of the top cell.
The filters can be modified to suit your needs, and to the format of your list.
Back to Main: Data validation in Main is done using ranges from DRange.
The script I use:
function onEdit(event) {
//SETTINGS
var dynamicSheet='DRange'; //sheet where the dynamic range lives
var tsheet = 'Main'; //the sheet you are monitoring for edits
var lcol = 2; //left-most column number you are monitoring; A=1, B=2 etc
var rcol = 5; //right-most column number you are monitoring
var tcol = 1; //column number in which you wish to populate the timestamp
//
var s = event.source.getActiveSheet();
var sname = s.getName();
if (sname == tsheet) {
var r = event.source.getActiveRange();
var scol = r.getColumn(); //scol is the column number of the edited cell
if (scol >= lcol && scol <= rcol) {
s.getRange(r.getRow(), tcol).setValue(new Date());
for(var looper=scol+1; looper<=rcol; looper++) {
s.getRange(r.getRow(),looper).setValue(""); //After edit clear the entries to the right
}
}
}
}
Original Youtube presentation that gave me most of the onEdit timestamp component:
https://www.youtube.com/watch?v=RDK8rjdE85Y
Continuing the evolution of this solution I've upped the ante by adding support for multiple root selections and deeper nested selections. This is a further development of JavierCane's solution (which in turn built on tarheel's).
/**
* "on edit" event handler
*
* Based on JavierCane's answer in
*
* http://stackoverflow.com/questions/21744547/how-do-you-do-dynamic-dependent-drop-downs-in-google-sheets
*
* Each set of options has it own sheet named after the option. The
* values in this sheet are used to populate the drop-down.
*
* The top row is assumed to be a header.
*
* The sub-category column is assumed to be the next column to the right.
*
* If there are no sub-categories the next column along is cleared in
* case the previous selection did have options.
*/
function onEdit() {
var NESTED_SELECTS_SHEET_NAME = "Sitemap"
var NESTED_SELECTS_ROOT_COLUMN = 1
var SUB_CATEGORY_COLUMN = NESTED_SELECTS_ROOT_COLUMN + 1
var NUMBER_OF_ROOT_OPTION_CELLS = 3
var OPTION_POSSIBLE_VALUES_SHEET_SUFFIX = ""
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var activeSheet = SpreadsheetApp.getActiveSheet()
if (activeSheet.getName() !== NESTED_SELECTS_SHEET_NAME) {
// Not in the sheet with nested selects, exit!
return
}
var activeCell = SpreadsheetApp.getActiveRange()
// Top row is the header
if (activeCell.getColumn() > SUB_CATEGORY_COLUMN ||
activeCell.getRow() === 1 ||
activeCell.getRow() > NUMBER_OF_ROOT_OPTION_CELLS + 1) {
// Out of selection range, exit!
return
}
var sheetWithActiveOptionPossibleValues = activeSpreadsheet
.getSheetByName(activeCell.getValue() + OPTION_POSSIBLE_VALUES_SHEET_SUFFIX)
if (sheetWithActiveOptionPossibleValues === null) {
// There are no further options for this value, so clear out any old
// values
activeSheet
.getRange(activeCell.getRow(), activeCell.getColumn() + 1)
.clearDataValidations()
.clearContent()
return
}
// Get all possible values
var activeOptionPossibleValues = sheetWithActiveOptionPossibleValues
.getSheetValues(1, 1, -1, 1)
var possibleValuesValidation = SpreadsheetApp.newDataValidation()
possibleValuesValidation.setAllowInvalid(false)
possibleValuesValidation.requireValueInList(activeOptionPossibleValues, true)
activeSheet
.getRange(activeCell.getRow(), activeCell.getColumn() + 1)
.setDataValidation(possibleValuesValidation.build())
} // onEdit()
As Javier says:
Create the sheet where you'll have the nested selectors
Go to the "Tools" > "Script Editor…" and select the "Blank project"
option
Paste the code attached to this answer
Modify the constants at the top of the script setting up your values
and save it
Create one sheet within this same document for each possible value of
the "root selector". They must be named as the value + the specified
suffix.
And if you wanted to see it in action I've created a demo sheet and you can see the code if you take a copy.

R: tm Textmining package: Doc-Level metadata generation is slow

I have a list of documents to process, and for each record I want to attach some metadata to the document "member" inside the "corpus" data structure that tm, the R package, generates (from reading in text files).
This for-loop works but it is very slow,
Performance seems to degrade as a function f ~ 1/n_docs.
for (i in seq(from= 1, to=length(corpus), by=1)){
if(opts$options$verbose == TRUE || i %% 50 == 0){
print(paste(i, " ", substr(corpus[[i]], 1, 140), sep = " "))
}
DublinCore(corpus[[i]], "title") = csv[[i,10]]
DublinCore(corpus[[i]], "Publisher" ) = csv[[i,16]] #institutions
}
This may do something to the corpus variable but I don't know what.
But when I put it inside a tm_map() (similar to lapply() function), it runs much faster, but the changes are not made persistent:
i = 0
corpus = tm_map(corpus, function(x){
i <<- i + 1
if(opts$options$verbose == TRUE){
print(paste(i, " ", substr(x, 1, 140), sep = " "))
}
meta(x, tag = "Heading") = csv[[i,10]]
meta(x, tag = "publisher" ) = csv[[i,16]]
})
Variable corpus has empty metadata fields after exiting the tm_map function. It should be filled. I have a few other things to do with the collection.
The R documentation for the meta() function says this:
Examples:
data("crude")
meta(crude[[1]])
DublinCore(crude[[1]])
meta(crude[[1]], tag = "Topics")
meta(crude[[1]], tag = "Comment") <- "A short comment."
meta(crude[[1]], tag = "Topics") <- NULL
DublinCore(crude[[1]], tag = "creator") <- "Ano Nymous"
DublinCore(crude[[1]], tag = "Format") <- "XML"
DublinCore(crude[[1]])
meta(crude[[1]])
meta(crude)
meta(crude, type = "corpus")
meta(crude, "labels") <- 21:40
meta(crude)
I tried many of these calls (with var "corpus" instead of "crude"), but they do not seem to work.
Someone else once seemed to have had the same problem with a similar data set (forum post from 2009, no response)
Here's a bit of benchmarking...
With the for loop :
expr.for <- function() {
for (i in seq(from= 1, to=length(corpus), by=1)){
DublinCore(corpus[[i]], "title") = LETTERS[round(runif(26))]
DublinCore(corpus[[i]], "Publisher" ) = LETTERS[round(runif(26))]
}
}
microbenchmark(expr.for())
# Unit: milliseconds
# expr min lq median uq max
# 1 expr.for() 21.50504 22.40111 23.56246 23.90446 70.12398
With tm_map :
corpus <- crude
expr.map <- function() {
tm_map(corpus, function(x) {
meta(x, "title") = LETTERS[round(runif(26))]
meta(x, "Publisher" ) = LETTERS[round(runif(26))]
x
})
}
microbenchmark(expr.map())
# Unit: milliseconds
# expr min lq median uq max
# 1 expr.map() 5.575842 5.700616 5.796284 5.886589 8.753482
So the tm_map version, as you noticed, seems to be about 4 times faster.
In your question you say that the changes in the tm_map version are not persistent, it is because you don't return x at the end of your anonymous function. In the end it should be :
meta(x, tag = "Heading") = csv[[i,10]]
meta(x, tag = "publisher" ) = csv[[i,16]]
x

Adding a variable to a Vertical Panel in GAS

I am working on a master sheet which should have a UiApp embedded into the active sheet. I have tried coding the first part with the labels in the first row, but I am stuck with getting the names[i] to appear on the left hand side of the table under the Tasks label.
I intend to make check boxes appear for every name under each task label.
Here is the code below:
function showTable()
{
var ss = SpreadsheetApp.getActive();
var TL = ss.getRange('B3').getValue();
//SetFontWeight("bold") for TL < *This does not work either* >
var startDate = ss.getRange('B2').getValue();
var strStartDate = startDate.getDate() + "/" + (startDate.getMonth() + 1) + "/" + StartDate.getFullYear();
var counta = ss.getRange('B4').getValue();
var app = UiApp.createApplication().setHeight(375).setWidth(620)
.setTitle('This is ' + TL + "'s Team Tasks for " + strStartDate);
var panel = app.createAbsolutePanel().setId('panel').setHeight(355).setWidth(605)
.setStyleAttribute('background', 'lightCyan');
var names = ss.getRange('B11:B').getValues();
for (var i = 0; 0 < names.length; i++) **//This is the part that does not work**
{
var agents = app.createLabel(names[i]);
}
var handler1 = app.createServerHandler('btnCloseWindow');
var btnCloseWindow = app.createButton('Close Window').addClickHandler(handler1).setStyleAttribute('background', 'lightYellow');
handler1.addCallbackElement(panel);
var myLabel0 = app.createLabel('Tasks');
var myLabel1 = app.createLabel('HW');
var myLabel2 = app.createLabel('MU');
var myLabel3 = app.createLabel('MOV');
panel.add(myLabel0, 40, 12)
panel.add(myLabel1, 100, 12)
panel.add(myLabel2, 140, 12)
panel.add(myLabel3, 175, 12)
panel.add(agents, 40, 30)
panel.add(btnCloseWindow, 490, 320)
app.add(panel);
ss.show(app);
return app;
};
function btnCloseWindow(e)
{
var ss = SpreadsheetApp.getActive();
var app = UiApp.getActiveApplication();
app.close();
return app;
};
How do you make a vertical panel with the names?
The names are defined in col B11:B and the tasks are defined in the labels.
This is my first time making a UiApp so any help is very much appreciated!
Try it like this, I don't know how the whole think should look like but the part with the list of names is now right...
function showTable()
{
var ss = SpreadsheetApp.getActive();
var TL = ss.getRange('B3').getValue()
ss.getRange('B3').setFontWeight('bold')
var startDate = ss.getRange('B2').getValue();
var strStartDate = Utilities.formatDate(startDate, ss.getSpreadsheetTimeZone(), 'dd/mm/yyyy');
var counta = ss.getRange('B4').getValue();
var app = UiApp.createApplication().setHeight(375).setWidth(620)
.setTitle('This is ' + TL + "'s Team Tasks for " + strStartDate);
var panel = app.createAbsolutePanel().setId('panel').setHeight(355).setWidth(605)
.setStyleAttribute('background', 'lightCyan');
var handler1 = app.createServerHandler('btnCloseWindow');
var btnCloseWindow = app.createButton('Close Window').addClickHandler(handler1).setStyleAttribute('background', 'lightYellow');
handler1.addCallbackElement(panel);
var myLabel0 = app.createLabel('Tasks');
var myLabel1 = app.createLabel('HW');
var myLabel2 = app.createLabel('MU');
var myLabel3 = app.createLabel('MOV');
panel.add(myLabel0, 40, 12)
panel.add(myLabel1, 100, 12)
panel.add(myLabel2, 140, 12)
panel.add(myLabel3, 175, 12)
var names = ss.getRange('B11:B').getValues();
Logger.log(names)
for(i=0;i<names.length;++i){
Logger.log(names[i][0]);// names is a 2D array, you only want the first and only column
if(names[i][0]!=''){ // don't show if empty
panel.add(app.createLabel(names[i][0])); //add label to the panel
}
}
panel.add(btnCloseWindow, 490, 320)
app.add(panel);
ss.show(app);
};
var names = ss.getRange('B11:B').getValues();
Should be,
var names = ss.getRange('B11:B1').getValues();
How I researched,
var data = ss.getRange("b11:b1").getValues();
data.forEach(function(element,index,array){Logger.log(index + " " + element )});
Logs, "0 dog
1 cat
2 frog
3 fish
4 hamster
5 dog
6 cat
7 frog
8 fish
9 hamster
10 dog"

Resources