I'm trying to copy a range from one sheet to another (whilst preserving the formulas). I wrote a simple script using copyTo:
function copyRangeAcrossSheets(source_sheet,source_range,target_sheet,target_range) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var source_sheet = spreadsheet.getSheetByName(source_sheet);
var target_sheet = spreadsheet.getSheetByName(target_sheet);
var source_range = source_sheet.getRange(source_range);
var target_range = target_sheet.getRange(target_range);
source_range.copyTo(target_range);
}
Which I call as followed:
=copyRangeAcrossSheets("TEST_source","A1:A3","TEST_target","A1:A3")
And I'm getting the below error:
You do not have the permission to call copyTo
I did some digging around and found that functions have to use special triggers (installable) in order to modify another file. However here I'm modifying the same file.
Q1: Why is copyTo failing here?
Q2: How can I workaround the issue without having to define installable triggers? (I just want to copy ranges whilst preserving formulas)
Why is it failing?
You cannot modify other any documents that require authorization via a custom function. The reason for this is that your function is executed as an anonymous user, which cannot obtain the necessary authorization to edit other sheets or documents of yours.
Reference: https://developers.google.com/apps-script/guides/sheets/functions#using_apps_script_services
Specific to you is this snippet:
Spreadsheet: Read only (can use most get*() methods, but not set*()).
Cannot open other spreadsheets (SpreadsheetApp.openById() or SpreadsheetApp.openByUrl()).
Also:
If your custom function throws the error message "You do not have permission to call X service.", the service requires user authorization and thus cannot be used in a custom function.
How can you work around this?
Write an Apps Script function that is executed via a trigger or manually, you can use onEdit or onChange triggers, or a time-based trigger. You can even manually run the function in the Apps Script IDE when you need to. This is the intended behavior of Apps Script.
Not sure about whether or not your data is persistent in your source spreadsheet, but you could always use the built-in IMPORTRANGE() function. Syntax is:
=IMPORTRANGE("SPREADSHEET_ID","SOURCE_SHEET!RANGE_START:RANGE_END")
Where SPREADSHEET_ID is the ID of the file you're working on.
Related
I'm traying to use http://medialize.github.io/URI.js/ to get a url form a cell in my google spreedsheet without the query part.
original url http://test.com/file.php?this=1&that=2 --> from this url i like to get http://test.com/file.php
following this site http://googleappscripting.com/working-with-urls/ i added the example code in my spreedsheet and i can get to functions to work like:
function urlPath(url){
return URI(url).path()
}
to get the url as i need, i created another function following :the documents here http://medialize.github.io/URI.js/
eval(UrlFetchApp.fetch('https://rawgit.com/medialize/URI.js/gh-pages/src/URI.js').getContentText());
function url_whitout(url){
return URI(url).search("")
}
but i get an error : TypeError: undefined is not a valid argument for URI
Any ideas how i can get this to work?
thanks
This is not a URI.js issue, it is a Google Apps Script issue. You need to get the url into the function first.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var url = ss.getActiveSheet().getRange(1,1).getValue();
The getRange() function has several different versions so choose wisely Range Docs
You have to get the value into the function for it to read it.
As a side note I recommend using the function
Logger.log(url);
to clarify what you are doing after you run the function.
In my source connector, I'm using javascript for my database work due to my requirements and parameters.
The end result is storing the data.
ifxResults = ifxConn.executeCachedQuery(ifxQuery); //var is declared
I need to use these results in the destination transformer.
I have tried channelMap.put("results", ifxResults);.
I get the following error ReferenceError: "channelMap" is not defined.
I have also tried to use return ifxResults but I'm not sure how to access this in the destination transformer.
Do you want to send each row as a separate message through your channel? If so, sounds like you want to use the Database Reader in JavaScript mode. Just return that ResultSet (it's really a CachedRowSet if you use executeCachedQuery like that) and the channel will handle the rest, dispatching an XML representation of each row as discrete messages.
If you want to send all rows in the result set aggregated into a single message, that will be possible with the Database Reader very soon: MIRTH-2337
Mirth Connect 3.5 will be released next week so you can take advantage of it then. But if you can't wait or don't want to upgrade then you can still do this with a JavaScript Reader:
var processor = new org.apache.commons.dbutils.BasicRowProcessor();
var results = new com.mirth.connect.donkey.util.DonkeyElement('<results/>');
while (ifxResults.next()) {
var result = results.addChildElement('result');
for (var entries = processor.toMap(ifxResults).entrySet().iterator(); entries.hasNext();) {
var entry = entries.next();
result.addChildElement(entry.getKey(), java.lang.String.valueOf(entry.getValue()));
}
}
return results.toXml();
I know this question is kind of old, but here's an answer just for the record.
For this answer, I'm assuming that you are using a Source connector type of JavaScript Reader, and that you're trying to use channelMap in the JavaScript Reader Settings editing pane.
The problem is that the channelMap variable isn't available in this part of the channel. It's only available in filters and transformers.
It's possible that what you want can be accomplished by using the globalChannelMap variable, e.g.
globalChannelMap.put("results", ifxResults);
I usually need to do this when I'm processing one record at a time and need to pass some setting to the destination channel. If you do it like I've done in the past, then you would first create a globalChannelMap key/value in the source channel's transformer:
globalchannelMap.put("ProcID","TestValue");
Then go to the Destinations tab and select your destination channel to make sure you're sending it to the destination (I've never tried this for channels with multiple destinations, so I'm not sure if anything different needs to be done).
Destination tab of source channel
Notice that ProcID is now listed in the Destination Mappings box. Click the New button next to the Map Variable box and you'll see Variable 1 appear. Double click on that and put in your mapping key, which in this case is ProcID.
Now go to your destination channel's source transformer. There you would enter the following code:
var SentValue = sourceMap.get("ProcID");
Now SentValue in your destination transformer has whatever was in ProcID when your source channel relinquished control.
I'm currently creating a spreadsheet where users can enter an item number and the sheet will return a description and price in the next two columns.
The spreadsheet pulls the item information from another sheet (not another page, rather a whole different URL) and the sheet updates itself every time an item number is entered (no vlookup because the info is on another URL).
Most likely, multiple people will need this form at the same time, and they will also need a copy for reference records. They all have access to the "Master File", the one I have, and I was hoping they could simply make a copy and then fill out the form.
However, while the code in my script works just fine on the Master File, when they make a copy the program won't run. I know it has to do with the triggers not being copied over and I've read up on writing triggers in the script, but here's the problem.
The users cannot see the script - that is, we don't want them to see any code. So they can't go in, turn on triggers via "Resources" or click the run/debug in script editor.
So basically I need a user to be able to open a shared, view only file, make a copy of a spreadsheet (which gets info from another sheet and has triggers), and use that spreadsheet buy inputting item numbers. Most of these people should not be able to see the inner workings, and wouldn't understand any of it anyway.
I was thinking a possible solution would be like what they do in this video around 25:56 or 37:355 where they can press a button and it writes the triggers. They don't go over how to do it though.
If your users are all in a private domain, your best solution would be to publish a private add-on (i.e. available only to domain users). That's not an option if you're using a consumer account.
Alternatively, you can use a menu-driven function to programmatically create the trigger(s) you need. This is effective in your case, because:
The "original" spreadsheet is shared as read-only, and users are expected to make a personal copy to enter their own data.
The users will be owners of their copies, so contained scripts will run as them and for them.
For example, you can try this shared spreadsheet. It's shared public, read-only, but if you save a copy, you will see a Custom Menu that sets a trigger function, and updates the first cell in the spreadsheet. Ten seconds late, the trigger function updates it again.
The demo script is contained in the spreadsheet, so you can see it for yourself there. Here's all it contains:
// Create a menu that will initialize the trigger
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Custom Menu')
.addItem('Initialize spreadsheet', 'setTrigger')
.addToUi();
}
function setTrigger() {
// clear any existing triggers
var triggers = ScriptApp.getUserTriggers(SpreadsheetApp.getActive())
for (var i=0; i<triggers.length; i++) {
ScriptApp.deleteTrigger(triggers[i]);
}
// set new trigger
ScriptApp.newTrigger("runTrigger")
.timeBased()
.after(10*1000) // 10s delay
.create();
announce("Trigger set. Wait for it...");
}
function runTrigger() {
announce("Trigger fired! This completes our demo.");
}
// Update first cell in spreadsheet
function announce(message) {
var range = SpreadsheetApp.getActive().getSheets()[0].getRange("A1");
range.setValue(message);
}
Instead of a menu, you could include a "button" image, and link a script to that. I didn't look at the video, but that's probably what they did. You can see more about that (silly, imho) option in How do you add UI inside cells in a google spreadsheet using app script?
From what I understood you don't need a script here, spreadsheet formulas will do the trick.
to import data from one spreadsheet to an other you can use the formula "importData" and put these data in a hiden sheet.
then you can use "vlookup" formula on this import or even better a "filter" formula (try the filter formula you'll love it).
I'm trying to make an onEdit trigger to send an email. I know that it doesn't work by default, as described here:
Because simple triggers fire automatically, without asking the user
for authorization, they are subject to several restrictions: They
cannot access services that require authorization. For example, a
simple trigger cannot send an email because the Gmail service requires
authorization, but a simple trigger can translate a phrase with the
Language service, which is anonymous.
But I thought I could make onEdit event to programmatically create another, time-driven trigger, which in turn will send the email. I know that both (1) my onEdit trigger works on its' own, and (2) manually running the function to programmatically create time-driven trigger to send email, work on its' own. But when I put the 2 inside 1, it doesn't work.
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssName = ss.getName();
var budgetLeft = ss.getRange("Test!B1").getValue();
var emailAddress = ss.getRange("Test!B4").getValue();
if (budgetLeft <= 4860) {
function SendEmailOnTrigger() {
MailApp.sendEmail({
to: emailAddress,
subject: "Warning: " + ssName,
htmlBody: "Sample Message",
});
}
}
function onEdit(e){
function createTimeDrivenTriggersTest() {
// Trigger TEST.
ScriptApp.newTrigger('SendEmailOnTrigger')
.timeBased()
.everyMinutes(1)
.create();
}
}
Does it "knows" that the time-driven trigger that would be created will send email, which is not acceptable when initiated with simple trigger? That's why it doesn't work? Thanks in advance for any help.
As Sandy Good explained, installable triggers inherit authorization from the process that created them. If it was created by you running an onEdit function manually, than onEdit acts as you and can create a trigger that acts as you. But if a function is executed by a simple trigger, it is only authorized to modify the spreadsheet to which it is bound, and do nothing else.
To see why this is needed, imagine that you when you opened someone's shared spreadsheet with a bound script, an onOpen function in that script installed a trigger that periodically forwards your email somewhere else.
So, just use an installable on-edit trigger instead of a simple one.
Here's how Google developers describe a good way of achieving what I intended to achieve: https://youtu.be/U9Ej6PCeO6s?t=2578
Make OnOpen create a menu item, clicking on which creates the required trigger. This menu item will copy over into duplicates of files, so one needs only to click the menu item to make it work.
I have this code:
<script src="http://maps.google.com/maps?file=api&v=2&sensor=false&key=babab" type='text/javascript'></script>
If the key is invalid then it pops up an alert, but I want to perform some action in this case. I'm not sure how to hook into it though. Any ideas?
Google does not offer an external method of checking the Google Maps API key. Hence you cannot query some service with e.g. "Is this code valid abcde1234" and get a TRUE/FALSE response.
There is a discussion on how the Maps API key is generated. But I suggest you look at a post from Mike Williams about the GValidateKey function. This is the function actually doing the magic validation - what it exactly does, like creating a hash from your Google account / domain - we don't know.
I see two solutions for your problem of checking whether the API key provided is correct:
Overwrite the incoming alert with some custom code (check for the content of the alert, or check if an alert occurs withing X seconds after page load)
Somehow get the GValidateKey function to validate your key beforehand. Maybe you can call it before referencing the API Javascript? Sounds kind of hackish to me...
The problem you will likely have is that you don't know what Google actually checks. The referrer, the referring site, the host - many possibilities (it is not the IP address of the server, but the name plus some additional information).
I just ran across the need to perform an action if an invalid API key was used. Google's documentation states:
If you want to programmatically detect an authentication failure (for example to automatically send an beacon) you can prepare a callback function. If the following global function is defined it will be called when the authentication fails.
This was all I needed to do:
function gm_authFailure() { // Perform action(s) }
For modern browsers (IE9+ and others) you may use DOMNodeRemoved event. You just need to add event handler to the element that you pass to the map constructor:
var map = new google.maps.Map(element, myOptions);
element.addEventListener("DOMNodeRemoved", function(e){
if (e.target === element){
//your code here
element.removeEventListener("DOMNodeRemoved", mapWasRemovedHandler, true);
}
}, false);