Library Defined Google Sheets Menu - javascript

I'm currently working on a way to distribute updates to Google Sheets without the user having to update anything at their end. This is in line with a specification I am having to meet.
This is so far accomplished using apps script libraries and boilerplate code for each sheets bound script.
A desire has now arisen to have custom menus within the sheets, whose menu text and functions are also centrally updatable. My thoughts on how to accomplish this were to have the menu items and their associated functions defined as followed in the library.
function getMenu() {
var menus = []
var obrMenu = {name: 'obrMenu', menuItems: []};
obrMenu['menuItems'].push({name: "Alert", func: "alert('Success')"});
menus.push(obrMenu);
return menus;
}
Then within the container scripts use the following to translate this into something usable to create the menus with.
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menus = lib.getMenu();
for (var i = 0; i < menus.length; i++) {
var cur = menus[i];
var items = cur['menuItems'];
var menuItems = [];
for (var j = 0; j < items.length; j++) {
var curFunc = new Function(
"return function " + items[j]['name'] + "(){" + items[j]['func'] + "}"
)();
menuItems.push({name: items[j]['name'], funcName: curFunc.name});
}
ss.addMenu(cur['name'], menuItems);
}
}
This method when ran, produces the error
Invalid argument: subMenus (line 15, file "Code")
with subMenus being the Apps Script argument name for what I have called menuItems. Whilst I gather this issue is probably down to the scoping of the produced functions, I can not seem to see how to get round it.
Any suggestions would be greatly appreciated. Many thanks.

Fix this line
menuItems.push({name: items[j]['name'], functionName: curFunc.name});
Using 'funcName' instead of 'functionName' as defined in GAS documentation is what messes it up. Apparently, the submenu object property names can't be changed.

Related

Replacing Text with replacetext() and defining said replacement as heading

I have a Google spreadsheet and a Google document. The document is a report which gets filled by the spreadsheet. The spreadsheet is also defining what comes into the report. Therefore I have a script, which gathers a bunch of placeholders depending on values in the the document.
After all the placeholders have been inserted in the document (there are a couple of pages before that) it looks kind of like this:
{{header1.1}}
{{text1.1}}//this is already a couple lines of text
{{table1.1}}
{{table.dir}}
{{blob1.1}}
{{blob.dir}}
I already have a script, which inserts all the text parts and I have set up a script, which should be capable of writing the tables at the correct position. So far I can replace the {{header1.1}}, but if I try to define it as a heading it works, but everything after the header1.1 is also a heading
I've been at this problem for quite a while and didn't get and its always one step forward one step back. Also this is my first question after a couple of years just reading on stackoverflow. I'd appreciate if someone could help.
function myUeberschriftenboi() {
doc = DocumentApp.openById('someID');
console.log(doc.getName());
var body = doc.getBody();
//formate
const plain3style = {};
plain3style[DocumentApp.Attribute.HEADING] = DocumentApp.ParagraphHeading.HEADING3;
var lvl2array = [ "{{header1.1}}" , "{{header1.2}}" ];
var fill2array = [ "Energy" , "Energyflow" ]
var lvl2count = 1;
for( var j = 0 ; j < lvl2array.length ; j++)
{
var seek = body.findText(lvl2array[j]);
if( seek != null)
{
body.replaceText(lvl2array[j] , "1.1."+lvl2count+" "+fill2array[j]+"\n");
var seek2 = body.findText("1."+lvl2count+" "+fill2array[j]);
seek2.getElement().getParent().getChild().setAttributes(plain3style);
lvl2count++;
}}}

Move Row to Other Sheet Based on Cell Value

I have a sheet with rows I'd like to move to another sheet based on a cell value. I tried following this post's solution (refer below), but I'm having trouble editing the script towards what I want it to do.
I'm doing Check-Ins for an event. I would like to be able to change the value in Column F, populate Column G with the time the status changed, and for the row data to migrate to the Attendee Arrived sheet.
I believe the script already does this, but one has to run it manually. It also takes care of deleting the row data in A (Event) after migrating it to B (Attendee Arrived).
My question is could someone please help me set it up in order for script to run continuously (on edit), and also accomplish all of the above if I missed something?
I don't believe the script will respect the drop down format as it runs so I'm willing to manually type in something. It'd be cool if it could stay that way though - makes it easier for one.
Here's the sheet I'm testing on.
https://docs.google.com/spreadsheets/d/1HrFnV2gFKj1vkw_UpJN4tstHVPK6Y8XhHCIyna9TLJg/edit#gid=1517587380
Here's the solution I tried following. All credit to Jason P and Ritz for this.
Google App Script - Google Spreadsheets Move Row based on cell value efficiently
Thank you D:
function CheckIn() {
// How Many Columns over to copy
var columsCopyCount = 7; // A=1 B=2 C=3 ....
// What Column to Monitor
var columnsToMonitor = 6; // A=1 B=2 C=3 ....
//TARGET SPREAD SHEETS
var target1 = "Attendee Arrived";
//Target Value
var cellvalue = "Attendee Arrived";
//SOURCE SPREAD SHEET
var ss = SpreadsheetApp.openById('1HrFnV2gFKj1vkw_UpJN4tstHVPK6Y8XhHCIyna9TLJg');
var sourceSpreadSheetSheetID = ss.getSheetByName("Event");
var sourceSpreadSheetSheetID1 = ss.getSheetByName(target1);
var data = sourceSpreadSheetSheetID.getRange(2, 1, sourceSpreadSheetSheetID.getLastRow() - 1, sourceSpreadSheetSheetID.getLastColumn()).getValues();
var attendee = [];
for (var i = 0; i < data.length; i++) {
var rValue = data[i][6];
if (rValue == cellvalue) {
attendee.push(data[i]);
} else { //Fail Safe
attendee.push(data[i]);
}
}
if(attendee.length > 0){
sourceSpreadSheetSheetID1.getRange(sourceSpreadSheetSheetID1.getLastRow() + 1,
1, attendee.length, attendee[0].length).setValues(attendee);
}
//Will delete the rows of importdata once the data is copided to other
sheets
sourceSpreadSheetSheetID.deleteRows(2,
sourceSpreadSheetSheetID.getLastRow() - 1);
}
Try this:
I imagine that you already know that you'll need an installable onEdit Trigger and that you can't test a function of this nature by running it without the event object.
function checkIn(e) {
var sh=e.range.getSheet();
if(sh.getName()!="Event") return;
if(e.range.columnStart==6) {
if(e.value=="Attendee Arrived"){
e.range.offset(0,1).setValue(Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "M/d/yyyy HH:mm:ss"));
var row=sh.getRange(e.range.rowStart,1,1,sh.getLastColumn()).getValues()[0];
e.source.getSheetByName("Attendee Arrived").appendRow(row);
sh.deleteRow(e.range.rowStart);
}
}
}

Script in google sheet to clear, but not remove filter

I am completely new in writing scripts for google sheets, so I was hoping some of you could help/guide me a little bit.
So Ideally, I want a script to clear (not remove) ALL filters in my sheet. This is, however, complicated for me to do (If some of you have such a script, I would LOVE to see it :) )
Instead, I made this one (Used recorder):
function Clear_Filter() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(1);
spreadsheet.getRange('B5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(2);
spreadsheet.getRange('C5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(3);
spreadsheet.getRange('G5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(7);
spreadsheet.getRange('J5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(10);
spreadsheet.getRange('M5').activate();
spreadsheet.getActiveSheet().getFilter().removeColumnFilterCriteria(13);
};
So my filter is set in Row 5. First I made the above for all columns (I had 20), but the problem is, that the code is very slow :( So now I am using the columns, that I use the most, when filtering, but the code is still slow. Well the worst thing is, that the code is running one column at a time (which we see in the code), and when the code is finish, I end up in the last column.
Can I do something? I dont want my sheet window keep turning right, when I run the code, and then end up in column M.
I will appreciate any help!
Thanks
Here is mine. The function does not remove filters. Instead, it clears them as requested.
function clearFilter(sheet) {
sheet = SpreadsheetApp.getActiveSheet(); //for testing purpose only
var filter = sheet.getFilter();
if (filter !== null) { // tests if there is a filter applied
var range = filter.getRange(); // prevents exception in case the filter is not applied to all columns
var firstColumn = range.getColumn();
var lastColumn = range.getLastColumn();
for (var i = firstColumn; i < lastColumn; i++) {
filter.removeColumnFilterCriteria(i);
}
Logger.log('All filters cleared')
}
else {Logger.log('There is no filter')}
}
Reset filters criterea + sort by first column (as default state).
And add this action to main menu.
/** #OnlyCurrentDoc */
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuEntries = [
{name: "Reset filters", functionName: "ResetFilters"},
];
ss.addMenu("Custom actions", menuEntries); // add to main menu
}
function ResetFilters() {
var spreadsheet = SpreadsheetApp.getActive();
var lastColumn = spreadsheet.getActiveSheet().getLastColumn();
var filter = spreadsheet.getActiveSheet().getFilter();
var criteria = SpreadsheetApp.newFilterCriteria().build();
for (var i = 1; i <= lastColumn; i++) {
filter.setColumnFilterCriteria(i, criteria);
}
filter.sort(1, true); // remove this line for disable setting of sorting order
};
To clear all
`function turnOffFilter(sheet) {
for (var index = 1; index < sheet.getLastColumn(); index++) {
if (sheet.getFilter().getColumnFilterCriteria(index)) {
sheet.getFilter().removeColumnFilterCriteria(index);
}
}
}`
It seems that the answers (e.g. proposed by Birmin) work fine but the script is painfully slow. I find it much faster to reapply the filter:
function clearFilter(sheet) {
sheet = SpreadsheetApp.getActiveSheet(); //for testing purpose only
var filter = sheet.getFilter();
if (filter !== null) { // tests if there is a filter applied
var range = filter.getRange();
filter.remove();
range.createFilter();
Logger.log('All filters cleared')
}
else {Logger.log('There is no filter')}
}
I, have you tried :
function Clear_Filter() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getFilter().remove();
}

How to add validation to existing google form items via script?

I am trying to add validation, specifically text validation, for my google form text items.
However, it looks to me like the 'setValidation()' function only works with items with known type like TextItem.
To my understanding, if I pull a form item via 'getItemById()', I would get a generic item. It still has 'TEXT' type but google script just doesn't see it as a TextItem and therefore the 'setValidation()' function is not available for it.
I have tried doing thing like .asTextItem() with no luck. Here is an example script that fails to run because of an error
'TypeError: Cannot find function setValidation in object Item. (line
10, file "Code")' on line 9.
function validationTest() {
var form = FormApp.getActiveForm();
var items = form.getItems();
var textValidation = FormApp.createTextValidation()
.requireNumberGreaterThanOrEqualTo(0)
.requireWholeNumber();
for (var i = 0; i<items.length; i++) {
items[i].asTextItem();
items[i].setValidation(textValidation);
};
}
So, is there a known solution or workaround for this issue? Thank you in advance.
SC
You should add .build() at the end of your validation builder, as it's shown here.
Also, asTextItem should be called simultaneously with setValidation:
function validationTest() {
var form = FormApp.getActiveForm();
var items = form.getItems();
var textValidation = FormApp.createTextValidation()
.requireNumberGreaterThanOrEqualTo(0)
.requireWholeNumber()
.build();
for (var i = 0; i<items.length; i++) {
items[i].asTextItem().setValidation(textValidation);
};
}

How can I dynamically index through datalayer tags in GTM?

I'm using the DuracellTomi datalayer plugin to push cart data from woocommerce to a GTM model to handle some tracking.
The DuracellTomi plugin pushes content to the transactionProducts[] array in the following format:
transactionProducts: Array[1]
0 : Object
category:""
currency:"USD"
id:8
name:"Test"
price:100
quantity:"1"
sku:8
I'd like to loop through this array and unstack it into three separate arrays, pricelist, skulist, and quantitylist. Currently I anticipate doing so as some variation on
//Get Product Information
if(stack = {{transactionProducts}}){
for(i = 0; i < stack.length; i++) {
if(stack.i.sku){
skulisttemp.i = stack.i.sku;
}
if(stack.i.price){
pricelisttemp.i = stack.i.price;
}
if(stack.i.sku){
quantitylisttemp.i = stack.i.quantity;
}
}
{{skulist}} = skulisttemp;
{{pricelist}} = pricelisttemp;
{{quantitylist}} = quantitylisttemp;
}
Obviously this is not going to work because of how the tag referencing is set up, but I'm wondering if anyone has dealt with this and knows what the best way to index through these arrays might be. (For those who don't know, the square bracket array call doesn't work with GTM variables and instead the . format is used instead.)
You would need to create 3 variable type custom javascript function that picks your required value from dataLayer and returns it in an array.
Something like
function(){
var products = {{transactionProducts}};
var skuArray = [];
for(i = 0; i < products.length; i++) {
if(products[i].sku){
skuArray.push(products[i].sku)
}
}
return skuArray
}
hope this helped you :)

Categories

Resources