How can I get the position of two worksheets using the Excel Javascript API?
Here is how it works just for one sheet:
Excel.run(function (ctx) {
var wSheetName = 'Sheet1';
var worksheet = ctx.workbook.worksheets.getItem(wSheetName);
worksheet.load('position')
return ctx.sync().then(function () {
console.log(worksheet.position);
});
});
=> it logs 0 to the console
But it doesn't logs anything if I try to get the position for two worksheets:
Excel.run(function (ctx) {
var wSheetName = 'Sheet1';
var wSheetName2 = 'Evars';
var worksheet = ctx.workbook.worksheets.getItem(wSheetName);
var worksheet2 = ctx.workbook.worksheets.getItem(wSheetName2);
worksheet.load('position')
worksheet2.load('position')
return ctx.sync().then(function () {
console.log(worksheet.position);
console.log(worksheet2.position);
});
});
I just tried your code, and it works fine. I wonder if you simply didn't have a sheet by one of those names, and so it was throwing an exception -- which was appearing to you as silent, since you didn't have a catch handler.
The code below, essentially the same as yours but with a catch statement, works correctly:
Excel.run(function(ctx) {
var wSheetName = 'Sheet1';
var wSheetName2 = 'Sheet2';
var worksheet = ctx.workbook.worksheets.getItem(wSheetName);
var worksheet2 = ctx.workbook.worksheets.getItem(wSheetName2);
worksheet.load('name, position')
worksheet2.load('name, position')
return ctx.sync().then(function () {
console.log(worksheet.name + ": " + worksheet.position);
console.log(worksheet2.name + ": " + worksheet2.position);
});
}).catch(function(error) {
OfficeHelpers.UI.notify(error);
OfficeHelpers.Utilities.log(error);
})
You can try this snippet live in literally five clicks in the new Script Lab (https://aka.ms/getscriptlab). Simply install the Script Lab add-in (free), then choose "Import" in the navigation menu, and use the following GIST URL: https://gist.github.com/Zlatkovsky/c61594f1c86970e8dba91fe94b7ca4b6. See more info about importing snippets to Script Lab.
Found the solution here ... maybe this will help someone
Excel.run(function (ctx) {
var worksheets = ctx.workbook.worksheets;
worksheets.load('items');
return ctx.sync().then(function () {
for (var i = 0; i < worksheets.items.length; i++) {
var sheet_name = worksheets.items[i].name;
var sheet_position = worksheets.items[i].position;
}
});
Related
So I'm needing to get the list of file names from a range of Google Drive URLs in a spreadsheet. Browsing around the net, I came across the code below. It works but only for the old style urls, which I heard Google changed in September 2021.
Note that links are not fully functional, please replace with real links to check!
The old style is:
https://drive.google.com/file/d/1GMUwYxZxsNpLiaYOiVMBwl41LpreQ-fc/view?usp=sharing
This works correctly from the code below.
What I'd like though is two things.
It should handle a range of a couple of columns, currently reading AE2:AE, and printing out on AM2:AM. What I'd like is to go through the range: AE2:AL and print out: AM2:AT
Secondly it should also handle the newer form urls:
https://drive.google.com/file/d/0B9EZQqsLDEqDUGlsdy1oVEtETGs/view?usp=sharing&resourcekey=0-h7HOcxayPaHJ5r6dAAslVQ
Current Code:
function getNames() {
var activeRange = SpreadsheetApp.getActiveSheet().getDataRange();
var height = activeRange.getHeight();
var links = SpreadsheetApp.getActiveSheet()
.getRange("AE2:AE" + height)
.getValues();
var nameValues = [];
links.forEach((row) => {
try {
var link = row[0];
var fileID = getIdFromLink(link);
var name = DriveApp.getFileById(fileID).getName();
nameValues.push([name]);
} catch (e) {
nameValues.push(["NO NAME FOUND"]);
}
});
var nameRange = SpreadsheetApp.getActiveSheet().getRange("AM2:AM" + height);
nameRange.setValues(nameValues);
}
function getIdFromLink(link) {
var regex = new RegExp(
/(?<=https:\/\/drive\.google\.com\/file\/d\/)(.+)(?=\/)/
);
return regex.exec(link)[0];
}
How should the code above be modified to enable what I'm wanting. Sorry, I tried a couple of if/else statements, but my Javascript knowledge is severely limited.
Any help would be greatly appreciated.
Current "screenshot" showing:
(1) - Old style url - correctly picking up file name (2)
(3) - New style url - not picking up file name (4)
Your getIdFromLink() function should work just fine as long as the files have not been shared in such a way that they require a resource key as well.
To work with resource keys, use DriveApp.getFileByIdAndResourceKey(), like this:
function getFileNamesByLink() {
const sheet = SpreadsheetApp.getActiveSheet();
const sourceRange = sheet.getRange('AE2:AL');
const targetRange = sheet.getRange('AM2');
const fileNames = sourceRange.getValues()
.map(row => row.map(link => getFileNameFromLink_(link)));
targetRange
.offset(0, 0, fileNames.length, fileNames[0].length)
.setValues(fileNames);
}
function getFileNameFromLink_(link) {
if (!link) {
return null;
}
const fileId = getIdFromLink_(link);
if (!fileId) {
return NaN;
}
let file;
try {
file = DriveApp.getFileById(fileId);
} catch (error) {
try {
file = DriveApp.getFileByIdAndResourceKey(fileId, getResourceKeyFromLink_(link));
} catch (error) {
return NaN;
}
}
return file.getName();
}
function getIdFromLink_(link) {
const match = String(link).match(/file\/d\/([-\w]+)/i);
return match ? match[1] : null;
}
function getResourceKeyFromLink_(link) {
const match = String(link).match(/resourcekey=([-\w]+)/i);
return match ? match[1] : null;
}
Note that the script may time out if you have thousands of links. If that happens, process the links in a piecemeal fashion, or see if the Advanced Drive Service works for you.
I have Google Sheet, name TEST https://docs.google.com/spreadsheets/d/1HsRwknyZBmZZ9nibDfNpOwqkVsFGThDyrTwspV-5_4U/edit?usp=sharing
Sheet: Arkusz 1
Column A: all people can edit
Column B: only owner can edit
Library (for everyone): https://script.google.com/macros/s/AKfycbzpnEMhIG-0dMp54q3W4UxoT71-lSdfF7Qxf7rq_j6gJMNIxuCS/exec
A user cannot add a row because it is blocked by column B, which belongs only to the admin.
How can I create macro, which allow user to add new rows?
I have three scripts:
function insertRow() {
var ss = SpreadsheetApp.getActive()
var sheetName = ss.getActiveSheet().getName()
var row = ss.getActiveRange().getRow()
var numRows = Browser.inputBox('Insert Rows', 'Enter the number of rows to insert', Browser.Buttons.OK);
Logger.log(numRows)
var url ="https://script.google.com/macros/s/AKfycbzpnEMhIG-0dMp54q3W4UxoT71-lSdfF7Qxf7rq_j6gJMNIxuCS/exec"
var queryString = "?sheetName="+sheetName+"&rowNo="+row+"&noOfRows="+numRows
url = url + queryString
Logger.log(url)
var request = UrlFetchApp.fetch(url)
if (request != 'Success')
Browser.msgBox(request)
}
Second:
function doGet(e) {
var param = e.queryString
var parameters = param.split("&")
// This just checks only 3 parameters are present else gives a invalid link
if (param != null && parameters.length == 3){
param = e.parameter
var name = param.sheetName
var row = Number(param.rowNo)
var numOfRows = Number(param.noOfRows)
} else{
return ContentService.createTextOutput("Invalid query")
}
try{
var ss = SpreadsheetApp.openById("https://docs.google.com/spreadsheets/d/1HsRwknyZBmZZ9nibDfNpOwqkVsFGThDyrTwspV-5_4U")
var sheet = ss.getSheetByName(name)
sheet.insertRowsAfter(row, numOfRows);
var source_range = sheet.getRange(row,1,1,sheet.getLastColumn());
var target_range = sheet.getRange(row+1,1,numOfRows);
source_range.copyTo(target_range);
}
catch (err){
return ContentService.createTextOutput("error: "+err)
}
return ContentService.createTextOutput("Success")
}
And after clicked function insertRow and filled number of rows I have doPost(e) information.
Could you help me?
On the solution you provided below, I see that the issue is in mainScript
function mainScript(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
// assign the sheet to a variable and use it below instead of spreadsheet
var sheet = spreadsheet.getSheetByName('ZNC')
sheet.getRange('A2').activate()
sheet.insertRowsBefore(sheet.getActiveRange().getRow(), 1);
}
Hmm, I created solution, but I think there's a bug somewhere, because it doesn't add the line, even though everything is correct and the script is published as public.
function ZNCWiersz() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('ZNC'), true);
const activeSheet = SpreadsheetApp.getActiveSheet().getSheetName();
const url = ScriptApp.getService().getUrl();
UrlFetchApp.fetch(`${url}?sheetName=${activeSheet}`, {
headers: { authorization: "Bearer " + ScriptApp.getOAuthToken() },
});
// DriveApp.getFiles() // This is used for automatically detecting the scope of "https://www.googleapis.com/auth/drive.readonly". This scope is used for the access token.
}
// When runScript() is run, this function is run.
const doGet = (e) => ContentService.createTextOutput(mainScript(e));
// This script is run by Web Apps.
function mainScript(e) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
spreadsheet.getSheetByName('ZNC')
spreadsheet.getRange('A2').activate()
spreadsheet.insertRowsBefore(spreadsheet.getActiveRange().getRow(), 1);
}
I have a working script. need to improvise to have no manual interruption. We have multiple Profiles in Analytics, sometimes we lose access and sometimes we have. So when i run the Script, If we lost access to 1 of 60 profiles, i have to delete that entry manually then rerun the script.
What i want is, If there is below error, Then skip and continue with next row
"GoogleJsonResponseException: API call to analytics.data.ga.get failed with error: User does not have sufficient permissions for this profile."
function GoogleAnalytics() {
var doc2 = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = doc2.getSheetByName("Dashboard");
for(var i=52;i<65;i++){
var viewId = dashboard.getRange(i,13).getValue(); // Your Google Analytics view ID
var metric = 'ga:metric, ga:metric2, ga:metric3';
var option = {'segment': 'gaid::-5'};
var result = Analytics.Data.Ga.get(viewId, metric, option);
var metric = result.totalsForAllResults['ga:metric'];
var metric2 = result.totalsForAllResults['ga:metric2'];
var metric3 = result.totalsForAllResults['ga:metric3'];
var doc = SpreadsheetApp.getActiveSpreadsheet(); // Current document
var sheet = doc.getActiveSheet(); // Current sheet
sheet.getRange(i,14,1,1).setValue(metric);
sheet.getRange(i,15,1,1).setValue(metric2);
sheet.getRange(i,16,1,1).setValue(metric3);
} }
try it this way:
function GoogleAnalytics() {
var doc2 = SpreadsheetApp.getActiveSpreadsheet();
var sh = doc2.getSheetByName("Dashboard");
var sheet = doc2.getActiveSheet(); // Current sheet
const vs = sh.getRange(52, 13, 13).getValues();
var metric = 'ga:metric, ga:metric2, ga:metric3';
var option = { 'segment': 'gaid::-5' };
for (var i = 0; i < vs.length; i++) {
var viewId = vs[i][0]; // Your Google Analytics view ID
try {
var result = Analytics.Data.Ga.get(viewId, metric, option);
}
catch(e){
continue;
}
if (result) {
sheet.getRange(i + 52, 14, 1, 3).setValues([[result.totalsForAllResults['ga:metric'], result.totalsForAllResults['ga:metric2'], result.totalsForAllResults['ga:metric3']]]);
}
}
}
Without the benefit of working data some of this may not be correct but using setValues and getValues should speed it up considerably and the try catch blocks should help with not getting result consistently. Also you want to avoid making unnecessary declarations in loops.
I might understand the question incorrectly (if so, please clarify) but it sounds to me like you just need to add...
function GoogleAnalytics() {
var doc2 = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = doc2.getSheetByName("Dashboard");
for(var i=52;i<65;i++){
try { //...this line and...
var viewId = dashboard.getRange(i,13).getValue(); // Your Google Analytics view ID
var metric = 'ga:metric, ga:metric2, ga:metric3';
var option = {'segment': 'gaid::-5'};
var result = Analytics.Data.Ga.get(viewId, metric, option);
var metric = result.totalsForAllResults['ga:metric'];
var metric2 = result.totalsForAllResults['ga:metric2'];
var metric3 = result.totalsForAllResults['ga:metric3'];
var doc = SpreadsheetApp.getActiveSpreadsheet(); // Current document
var sheet = doc.getActiveSheet(); // Current sheet
sheet.getRange(i,14,1,1).setValue(metric);
sheet.getRange(i,15,1,1).setValue(metric2);
sheet.getRange(i,16,1,1).setValue(metric3);
} catch(e) { //...this part
console.log(e); //optional, catch(e){} is perfectly valid as well, or any code you might want to execute on error
}
} }
EDIT: ANSWER BELOW
I'm making my first JavaScript project and decided to make a simple weather app. It fetches weather data of a city you put in from the openweathermap.org api and displays it in a table. I firstly made it using fetch() and .then. I then learned about async functions and the await keyword. After converting the script to an asynchronous function, I came across a problem. If the first city you enter isn't a real city (an error is catched while fetching the api), the warning message appears, BUT the table also appears because the rest of the function still executes.
So my question is: how can I stop the async function if any errors are catched?
Here's the website: https://lorenzo3117.github.io/weather-app/
Here's the code:
// Launch weather() function and catch any errors with the api request and display the warning message if there are any errors
function main() {
weather().catch(error => {
document.querySelector("#warningMessage").style.display = "block";
console.log(error);
});
}
// Main function
async function weather() {
// Take city from input and reset input field
var city = document.querySelector("#cityInput").value;
document.querySelector("#cityInput").value = "";
// Get api response and make it into a Json
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=<apiKey>&units=metric");
const jsonData = await apiResponse.json();
// Removes warning message
document.querySelector("#warningMessage").style.display = "none";
// Puts the Json into an array and launches createTable function
var arrayJson = [jsonData];
createTable(document.querySelector("#table"), arrayJson);
// Function to create the table
function createTable(table, data) {
// Makes the table visible
document.querySelector("#table").style.display = "block";
// Goes through the array and makes the rows for the table
for (let i = 0; i < data.length; i++) {
let rowData = data[i];
var row = table.insertRow(table.rows.length);
// This var exists to make the first letter capitalized without making a gigantic line (see insertCell(3), line 53)
// Could be made into a function if needed
var weatherDescription = rowData.weather[0].description;
// Take latitude and longitude for google maps link
var lat = rowData.coord.lat;
var long = rowData.coord.lon;
// Make an a-tag for link to google maps
var mapLink = document.createElement("a");
mapLink.innerHTML = "Link";
mapLink.target = "_blank";
mapLink.href = "https://www.google.com/maps/search/?api=1&query=" + lat + "," + long;
// Making rows in table
row.insertCell(0).innerHTML = rowData.name + ", " + rowData.sys.country;
row.insertCell(1).innerHTML = rowData.main.temp + " °C";
row.insertCell(2).innerHTML = rowData.main.humidity + "%";
row.insertCell(3).innerHTML = weatherDescription.charAt(0).toUpperCase() + weatherDescription.slice(1);
row.insertCell(4).appendChild(mapLink); // appendChild for anchor tag because innerHTML only works with text
}
}
And the repo: https://github.com/lorenzo3117/weather-app
Thank you
you can do this :
async function weather() {
try {
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=02587cc48685af80ea225c1601e4f792&units=metric");
} catch(err) {
alert(err); // TypeError: failed to fetch
return;
}
}
weather();
Actually, the error catched isn't an error with the api itself because the api still sends a json, but the error is catched while trying to read a certain object from the json (which doesn't exist because the json isn't a normal one with weather data). Therefore the function stops far later than expected, after the table was made visible.
I just put the line that made the table visible after the function that creates the table (after where the real error occurs). Also thanks #Dadboz for the try catch method which made the code even more compact. I also added an if else to check if the json file is the correct one so unnecessary code doesn't get executed. Thanks #James for pointing this out to me.
Here's the final code:
// Main function
async function weather() {
try {
// Take city from input and reset input field
var city = document.querySelector("#cityInput").value;
document.querySelector("#cityInput").value = "";
// Get api response and make it into a Json
const apiResponse = await fetch("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&appid=<apiKey>&units=metric");
const jsonData = await apiResponse.json();
if (jsonData.message == "city not found") {
document.querySelector("#warningMessage").style.display = "block";
} else {
// Removes warning message
document.querySelector("#warningMessage").style.display = "none";
// Puts the Json into an array and launches updateTable function
var arrayJson = [jsonData];
updateTable(document.querySelector("#table"), arrayJson);
}
}
catch (error) {
console.log(error);
}
}
// Function to update the table
function updateTable(table, data) {
// Goes through the array and makes the rows for the table
for (let i = 0; i < data.length; i++) {
let rowData = data[i];
var row = table.insertRow(table.rows.length);
// This var exists to make the first letter capitalized without making a gigantic line (see insertCell(3), line 53)
// Could be made into a function if needed
var weatherDescription = rowData.weather[0].description;
// Take latitude and longitude for google maps link
var lat = rowData.coord.lat;
var long = rowData.coord.lon;
// Make an a-tag for link to google maps
var mapLink = document.createElement("a");
mapLink.innerHTML = "Link";
mapLink.target = "_blank";
mapLink.href = "https://www.google.com/maps/search/?api=1&query=" + lat + "," + long;
// Making rows in table
row.insertCell(0).innerHTML = rowData.name + ", " + rowData.sys.country;
row.insertCell(1).innerHTML = rowData.main.temp + " °C";
row.insertCell(2).innerHTML = rowData.main.humidity + "%";
row.insertCell(3).innerHTML = weatherDescription.charAt(0).toUpperCase() + weatherDescription.slice(1);
row.insertCell(4).appendChild(mapLink); // appendChild for anchor tag because innerHTML only works with text
}
// Makes the table visible
document.querySelector("#table").style.display = "block";
}
Thanks everyone for your answers, have a good day!
Lorenzo
I'm trying to take my project from a stage to another, and I was able to make some good progress so far.
I've got the following script that runs when the sheet called Setup_Protections is edited: it removes all the sheets protections then add them back with the Emails specified in the Setup sheet (i.e. add those emails as editors of the protected sheets).
But the problem is that the spreadsheet needs to be shared beforehand so they can access it first. Is there a way to share in the same time the document with the emails entered in the Setup sheet ? (without necessary using a method that requires enabling Sheets API as I'll be duplicating many times the documents)
Thank you for your help
Sheet
MY SCRIPT:`
var environment = {
protectionConfigSheetName: "Setup_Protection",
};
// Script fires when Setup_Protection is edited
function onEdit(e) {
if (e.range.getSheet().getName() === environment.protectionConfigSheetName)
resetSpreadsheetProtections();
}
function removeSpreadsheetProtections(spreadsheet) {
[
SpreadsheetApp.ProtectionType.SHEET,
].forEach(function (type) {
return spreadsheet.getProtections(type).forEach(function (protection) { return protection.remove(); });
});
}
function getProtectionConfig(spreadsheet) {
var protectionConfigSheetName = "Setup_Protection";
var sheet = spreadsheet.getSheetByName(environment.protectionConfigSheetName);
var values = sheet.getDataRange().getValues();
var protectionConfig = values
.slice(1)
.reduce(function (protectionConfig, _a) {
var targetSheetName = _a[0], emailAddress = _a[1];
var config = protectionConfig.find(function (_a) {
var sheetName = _a.sheetName;
return sheetName === targetSheetName;
});
var editors = emailAddress.split(",");
if (config)
config.editors = config.editors.concat(editors);
else
protectionConfig.push({
sheetName: targetSheetName,
editors: editors.slice()
});
return protectionConfig;
}, []);
return protectionConfig;
}
function setSpreadsheetProtections(spreadsheet, protectionConfig) {
spreadsheet.getSheets().forEach(function (sheet) {
var protection = sheet.protect();
protection.removeEditors(protection.getEditors().map(function(editor) {
return editor.getEmail();
}));
var currentSheetName = sheet.getName();
var config = protectionConfig.find(function (_a) {
var sheetName = _a.sheetName;
return sheetName === currentSheetName;
});
if (config)
protection.addEditors(config.editors);
});
}
function resetSpreadsheetProtections() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var protectionConfig = getProtectionConfig(spreadsheet);
removeSpreadsheetProtections(spreadsheet);
setSpreadsheetProtections(spreadsheet, protectionConfig);
}
Note: there is also another script needed for this one called Polyfill.gs
Finally it's working now:
Add the following to the above code:
function addEditorsToSpreadsheetFromProtectionConfig(spreadsheet, protectionConfig) {
var editors = protectionConfig.reduce(function (accumulator, _a) {
var editors = _a.editors;
return accumulator.concat(editors);
}, []);
spreadsheet.addEditors(editors);
}
Then Add to resetSpreadsheetProtections() the following line:
addEditorsToSpreadsheetFromProtectionConfig(spreadsheet, protectionConfig);