Unable to get Remove() Protect() working with Google Sheets - google-sheets-api

I want to give users of a Google Sheet the ability to protect a row in a sheet. By setting a value in a cell to "√" (from a dropdown list) I want to protect the row. If the same or any other user wants to change a value in the row, I want them to be able to remove "√" with "-" (also from the dropdown list).
I'm using the onEdit() event.
I'm able to protect the desired range, but having problems removing the protection, remove() just isn't working.
I can get remove to work in the following command, but what happens is protect() simply creates a new protected range and remove() removes it. I'm trying to remove a range created by someone else, some time in the past.
var result = range.protect().remove()
I'm about 8 hours into this (mostly searching for an answer).
Thank you in advance for any assistance.

To remove a protected range, you have to get it by listing the protections:
// Remove all range protections in the spreadsheet that the user has permission to edit.
var ss = SpreadsheetApp.getActive();
var protections =
ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
See the samples/docs # https://developers.google.com/apps-script/reference/spreadsheet/protection. Calling range.protect() will always create a new protection.

Related

How to save as image a QR codes created in Google Sheets with api.qrserver?

I am generating QR codes in a column in Google Sheets with data entered via a Google Forms, and I would like to save them as images.
Using Ctrl + C and Ctrl+V works only inside the Google Sheet and trying to copy and paste that one outside the sheet doesnt work either. The clipboard is detected as not containing images to paste elsewhere.
The problem is not in generating the QR, but exporting or saving it as image.
To generate the QR's I'm using the api.qrserver, but the documentation for this only goes about creating or reading QR's [http://goqr.me/api/doc/] , for which this base is used to technically generate a PNG image:
http(s)://api.qrserver.com/v1/create-qr-code/?data=[URL-encoded-text]&size=[pixels]x[pixels]
However, If I use that formula externally to link directly to the cell, the PNG created only gives the URL address of the Google Sheet and not the information of the QR.
This is the array formula I'm using at row 1 to generate the QR's:
={"QR";arrayformula( if(
len(INDIRECT("A2:A")),IMAGE("https://api.qrserver.com/v1/create-qr-
code/?size=120x120&data="&ENCODEURL(
"-Tip: "&indirect("B2:B")& char(10)&
"-Ub: "&INDIRECT("C2:C")& char(10)&
"-#: "&indirect("D2:D")& char(10)&
"-IDE: "&INDIRECT("E2:E")& char(10)&
"-Serial: "&INDIRECT("G2:G") & char(10)&
"-Pa: "&indirect("H2:H") & char(10)&
"-IP: "&INDIRECT("I2:I") & char(10)&
"-As: "&indirect("J2:J") & char(10)&
"-R: "&INDIRECT("K2:K") & char(10)&
"-Ar: "&indirect("L2:L") & char(10)
),2)
,""))}
Ideally, I would like to automatically make the QR's as images and save them to a folder in google drive automatically. If possible, assigning them an IDE value that is contained in another column as well as in the same QR code.
Here is the example for the QR generation:
[https://docs.google.com/spreadsheets/d/16pALQ0LNbBqqdRNNejar8zJJPPNEP8I7HSbKDOJHZaw/edit?usp=sharing ]
You want to save the QR codes in the Spreadsheet as the image files using Google Apps Script.
In this case, you want to achieve this without changing the Spreadsheet.
Values are put to from the column "A" to "L", when the form was submitted.
If my understanding is correct, how about this sample script? Please think of this as just one of several answers.
In this sample script, the QR code is directly retrieved from api.qrserver.com and those are saved as the image files, because in your situation, the images cannot be retrieved from the Spreadsheet. In this case, all parameters of QR code are used from the Spreadsheet.
Sample script:
Before you run the script, please set the variables of sheetName and folderId. When you run this script, all QR codes in the sheet of Hoja_calculos are created as the image files.
function myFunction() {
var sheetName = "Hoja_calculos"; // Sheet name is from your shared Spreadsheet.
var folderId = "###"; // Folder ID of the folder where the image files are created.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sheetName);
var url = "https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=";
var keys = ["-Tipo:", "-Ubicación:", "-# registro:", "-ID-C:", "-# Serial:", "-Pas:", "-Di I:", "-Asi:", "-RP:", "-Ar:"];
var values = sheet.getRange(2, 2, sheet.getLastRow() - 1, 11).getValues()
.filter(function(e) {return e.some(function(f) {return f})})
.map(function(row) {
row.splice(4, 1);
return row;
});
var length = values.length;
// values = [values.pop()]; // If you want to save only the QR code of the last row, please use this line.
var reqs = values.map(function(e) {return {url: url + encodeURIComponent(keys.map(function(f, j) {return f + e[j]}).join("\r"))}});
var res = UrlFetchApp.fetchAll(reqs);
res.forEach(function(r, i) {
var blob = r.getBlob().setName(length == values.length ? "row" + (i + 2) + ".png" : "row" + (length + 1) + ".png");
DriveApp.getFolderById(folderId).createFile(blob);
});
}
Note:
If you want to save only the QR code of the last row, please use this line. At that time, please remove // of // values = [values.pop()];. By this, only the QR code of the last row is retrieved.
Because I thought that above situation might be required for your situation, I added it. For example, by using the installable trigger to the function, when the form was submitted, only QR code of the last row is created as the image file.
But I'm not sure about your environment of the form. So I'm not sure whether this can be used for your situation. I apologize for this.
I confirmed that this script worked for your shared Spreadsheet. If the actual Spreadsheet is different from this, please modify the script, because it might not work fine.
References:
Class UrlFetchApp
Class DriveApp
You can not use a custom function and interacts with Google Drive, there is a limitation to access user data using custom function and onEdit function on G script:
https://developers.google.com/apps-script/guides/sheets/functions
AFAIK the only way to achieve this is creating a custom menu and interacts with a custom function by menu.
var url = "https://api.qrserver.com/v1/create-qr-code/?data=Sample&size=128x128";
var folders = DriveApp.getFoldersByName("QRCodeFolderName");// Put here the name of the folder inside root folder
var folder = folders.next();
var response = UrlFetchApp.fetch(url);
if (response.getResponseCode() == 200){
var fileBlob = response.getBlob();
folder.createFile(qrcodevalue+".png",fileBlob);
}

Using a loop within the menu UI script beginner

I am trying to create a dynamic menu that creates the names of the sheets in it. I dont often code and need some help. currently the code ON_Open creates a menu, creates its first item in the menu, then add a seperator and then goes into a loop. it checks how many sheets there are and starts at the first one. stores the name and makes a menu item with that name, then advances to the next sheet. gets its name and makes the next menu item. i can get the loop to work with the menu UI syntax.im not worried about the names. i will try to figure that out next,just want it to create the menus first
function onOpen() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var numsheets = spreadsheet.getNumSheets();
SpreadsheetApp.getUi
.createMenu('SWMS CREATER')
.addItem('Create New SWMS', 'showPrompt')
.addSeparator()
for ( var i = 0; i < numsheets.length;i++ ) {
var ui = SpreadsheetApp.getUi();
var subMenu = ui.createMenu('Test Menu');
subMenu.addItem('Test script'i ,'showPrompt');
}
}
The OP is trying to create a dynamic menu that lists each of the sheets in the spreadsheet. The OP's code is very close to working - there are just a small, but significant, number of adjustments.
function onOpen() {
var ui = SpreadsheetApp.getUi();
var menu = ui.createMenu('OPSWMS CREATER')
.addItem('Create New SWMS', 'showPrompt')
.addSeparator();
var sheetList = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var subMenu = ui.createMenu('OPTest Menu');
for (var i = 0; i < sheetList.length; i++) {
subMenu.addItem(sheetList[i].getName(), 'showPrompt');
}
menu.addSubMenu(subMenu).addToUi();
}
Summary of major differences:
1) variable ui moved out of the loop; and then re-used where possible.
2) variable menu established and also moved out of the loop. This is re-used to add the subMenu in the last line of code.
3) added a semi-colon after .addSeparator() (though optional)
4) used .getSheets() to get all the sheets. This is the first key element missing from the OP code.
5) dropped var numsheets line. You don't need this because you can get the same count on sheetList.
6) within the loop, two things to note
sheetList[i] the i in square brackets ([i]) returns the relevant item from "sheetList";
.getName() returns the name of the sheet. Combined, sheetList[i].getName() gives you the name of the sheet, and lets you add it as a menu item selection.
7) menu.addSubMenu(subMenu).addToUi(); This add the contents of the loop to the menu. This is the second key element missing from the OP code.
Credit:
Akshin Jalilov's answer to Google Apps Script: Dynamically creating spreadsheet menu items

Google SpreadSheet Scripts: how get filtered DataRange

I'm using
sheet.getDataRange()
to get all cells. It fetch all rows even hidden (that was filtered).
How can I get only not hidden rows (the rows that user see when he/she opens a spreadsheet)?
Thank you.
Actually, there is already an existing issue on Integration of Tools/Filter with google apps script and, based on that issues tracker, this feature hasn't been added yet.
You may, however, try alternative solutions given in this SO post - Google Script Filter Or Hide Row. One of the solutions given is by reducing a set of data with the use of an array and a hide method as shown below.
function MasterFilter() {
var headers = 4; // # rows to skip
var sheet = SpreadsheetApp.getActiveSheet();
var maxRows = sheet.getMaxRows();
//show all the rows
sheet.showRows(1, maxRows);
//get data from column B
var data = sheet.getRange('B:B').getValues();
//iterate over all rows
for(var i=headers; i< data.length; i++){
if(data[i][0] == false){
sheet.hideRow(sheet.getRange(i+1,1));
}
}
}
I hope one of the solutions given works for you. :)

Check Word Doc for Field Code before applying

So I've coded a VSTO addin using vb.net to add a header to a document in Word however from historical methods we have lots of templates with field codes. My addin does not account for these and simply strips the header to add xxxxx value you choose from the pop up.
I need my code to be smart enough to 'spot' the field code and append or if it does not exist e.g. a blank document then continue running as expected. I can append this field code using the below code:
wordDocument.Variables("fieldname").Value = "xxxx"
wordDocument.Fields.Update
However my tool then adds the header as normal and strips most the content from the template. So effectively my question is how would I code a check for this before proceeding. So in plain English I would need my addin to go from this:
Load pop up
Set xxxx value in header
Close
To this:
Load pop up
Check Document for existing "fieldname"
If "fieldname" exists then
wordDocument.Variables("fieldname").Value = "xxxx" (from pop up selection)
wordDocument.Fields.Update
However if "fieldname" doesn't exist then continue as normal....
Sorry if this is a little complex and/or long winded.
Thanks in advance.
Here is my code in C#, hope this might help you to code in VB.Net
foreach (Section sec in doc.Sections)
{
doc.ActiveWindow.View.set_SeekView(WdSeekView.wdSeekCurrentPageHeader);
foreach (HeaderFooter headerFooter in sec.GetHeadersFooters())
{
doc.ActiveWindow.View.set_SeekView(headerFooter.IsHeader ? WdSeekView.wdSeekCurrentPageHeader : WdSeekView.wdSeekCurrentPageFooter);
if (headerFooter.Range.Fields.Count > 0)
{
//Append to existing fields
UpdateFields(headerFooter.Range.Fields);
}
else
{
//Add field code
AddFieldCode(headerFooter.Range);
}
}
doc.ActiveWindow.View.set_SeekView(WdSeekView.wdSeekMainDocument);
}
Extension method to iterate through the header types
public static IEnumerable<HeaderFooter> GetHeadersFooters(this Section section)
{
List<HeaderFooter> headerFooterlist = new List<HeaderFooter>
{
section.Headers[WdHeaderFooterIndex.wdHeaderFooterPrimary],
section.Headers[WdHeaderFooterIndex.wdHeaderFooterFirstPage],
section.Headers[WdHeaderFooterIndex.wdHeaderFooterEvenPages],
section.Footers[WdHeaderFooterIndex.wdHeaderFooterPrimary],
section.Footers[WdHeaderFooterIndex.wdHeaderFooterFirstPage],
section.Footers[WdHeaderFooterIndex.wdHeaderFooterEvenPages]
};
return headerFooterlist;
}

Dojo EnhancedGrid and programmatic selection

Here's my problem: in my application I have a Dojo EnhancedGrid, backed up by an ItemFileReadStore. The page flow looks like this:
The user selects a value from a selection list.
The item from the list is posted on a server and then the grid is updated with data from the server (don't ask why, this is how it's supposed to work)
The new item is highlighted in the grid.
Now, the first two steps work like a charm; however, the third step gave me some headaches. After the data is successfully POSTed to the server (via dojo.xhrPost() ) the following code runs:
myGrid.store.close();
myGrid._refresh();
myGrid.store.fetch({
onComplete : function(items) {
for ( var i = 0; i < items.length; i++) {
if (items[i].documentType[0].id == documentTypeId) {
var newItemIndex = myGrid.getItemIndex(items[i]);
exportMappingGrid.selection.deselectAll();
exportMappingGrid.selection.addToSelection(newItemIndex);
}
}
}
});
Now, the selection of the grid is updated (i.e. the selection object has a selectedIndex > 0), but visually there's no response, unless I hover the mouse over the "selected" row. If I remove the .deselectAll() line (which I suspected as the culprit) then I sometimes end up with two items selected at once, although the grid selectionMode attribute is set to single.
Any thoughts on this one?
Thanks a lot.
You need to use setSelected(), like so
exportMappingGrid.selection.setSelected(newItemIndex, true);
The second parameter is true to select the row, false to unselect it.
This is what works for me:
grid.selection.clear();
grid.selection.addToSelection(newItemIndex);
grid.selection.getFirstSelected();
Jon