Remove and add protection within onEdit script - arrays

CURRENT SITUATION
As an owner I've created a spreadsheet that makes an editor of everyone who has received the link. However there are some partially protected sheets with a few unprotected columns, making it possible for these editors to change values. I have an onEdit function that automatically sorts the entire range of a sheet when a cell has been edited by user.
PROBLEM
Unfortunately, since these sheets are partially protected, the AUTOSORT does not work. I need to add something to the script so that when the onEdit is triggered, the activesheet becomes unprotected and the AUTOSORT can do its job. After that the protection is added again (with a few columns as an exception). I don't want everyone to lose their editors rights after this. So it needs to go back to normal.
Script that I tried so far but not working:
function onEdit() {
if (e.range.columnStart == 3 && e.range.getValue() != '') {
var sheets = ["FASHION NL", "FASHION BE","KIDS & UNDERWEAR BNL" ,"NEW BUSINESS BNL" ,"SPORTS & SHOES BNL", "HD&E BNL"]; // Please set your expected sheet names.
var sheet = e.range.getSheet();
if (sheets.includes(sheet.getSheetName())) {
var range = sheet.getRange("A5:bY600");
var spreadsheet = SpreadsheetApp.getActive();
var allProtections =
spreadsheet.getActiveSheet().getProtections(SpreadsheetApp.ProtectionType.SHEET);
var protection = allProtections[0];
protection.remove();
range.sort({ column: 11, ascending: true });
e.source.toast('Sort complete.');
}
}
var protection = spreadsheet.getActiveSheet().protect();
protection.setUnprotectedRanges([spreadsheet.getRange('AK:AK'),
spreadsheet.getRange('BN:BN')])
var editors = SpreadSheet.getEditors();
for (var i = 0; i < editors.length; i++) {
SpreadSheet.removeEditor(editors[i])
}
}
CLARIFICATION PROBLEM
It does not give me an error, but script fails to sort the data range.
SAMPLE SHEET STRUCTURE
Trigger is on column C (3)
Script is created in a way that it reflects whether the Activesheet where the edit takes place is one of the predetermined range of sheets.

You should keep all your permissions as they're and set the function as if it's run by yourself (delete all the parts that have to do with removing and adding permissions). Call it other way than onEdit, for example autoSort(), and set an installable trigger run by event. You can read about it here
The sorting then would be run as it was done by you but triggered by other users' changes. Give it a try and let me know!

Related

data range copy automation onchange trigger

I try to run the script when range has changed and meets a specific value. value comes by using vlookup with arrayformula.
Script work only when specifically i activate the cell by entering the value.
I want this automatically range copy when meet a certain value without clicking on it.
I already try onEdit and onchange triggers it's not working. please help me if there is any way to automate the scrip
Value=Done
column=54
enter image description here
var ss=SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DRW");
var active=ss.getActiveCell();var row=active.getRow();var col=active.getColumn();
var val=active.getValue();var source=ss.getRange("B"+row+":P"+row).getValues();
var destination=SpreadsheetApp.getActiveSpreadsheet().getSheetByName("BOM");
var last=destination.getRange("A1:A").getValues().filter(String).length+1
if(col==30 && val=="Done")
{
destination.getRange("B"+last+":P"+last).setValues(source);
destination.getRange("A"+last+":A"+last).setValue(new Date());Browser.msgBox("Data Sent To BOM")
ss.getRange('AE1:AF1').activate();
ss.getActiveRangeList().clear({ contentsOnly: false, skipFilteredRows: false });}

How do I read numbers with appscript from a google sheet and want to use the number as formatted on another sheet"

I am doing a dependant dropdown list in google sheets using a script.
The source sheet data is read into an array, The destination sheet pulls the dropdown options from the aray.
The numbers from the source sheet are formatted as degrees; 0°, 45°, 90°, 180°.
When the are displayed in the dropdown on the destination sheet the degree symbol is removed and the 0 option doesn't show as an option at all. so I get 45, 90, 180. If I format the destination cell as percent, the the chosen option doesn't match the validation. and I still can't get the 0°.
How can I pass the formatting with the numbers so everything works correctly?
I was thinking I need to do something with setNumberFormat('##0"°"'), but I don't know how.
This is how I am creating the data from the array:
var ws = SpreadsheetApp.getActiveSpreadsheet() .getSheetByName(mainWsName) ;
var wsOptions = SpreadsheetApp.getActiveSpreadsheet() .getSheetByName(optionsWsName) ;
var options = wsOptions.getRange(3, 2,wsOptions.getLastRow()-2, 5).getValues();
var optionsFormat = wsOptions.getRange(3, 2,wsOptions.getLastRow()-2, 5).getNumberFormat()
This is how the data is being retrieved from the array:
var filteredOptions = options.filter(function(o){ return o[0] === firstLevelColValue && o[1] === val });
var listToApply = filteredOptions.map(function(o){ return o[2] });
var cell = ws.getRange(r, thirdLevelColumn) ;
applyValidationToCell(listToApply,cell);
This is my first time asking a question here, I hope I have given the information necessary to solve my problem. Let me know if there is additional information needed.
Thanks
Use getDisplayValues() instead of getValues()
This will allows you to retrieve the values in exactly the same way as they are displayed in the spreadsheet.

Google Sheets - Allow only one checkbox to be checked when the boxes are placed in specific cells across a wide range

I need a simple script for Google Sheets to allow only one of 6 checkboxes to be checked at a time. The end user will check a checkbox and should see the one they checked before turn unchecked or FALSE when they check the new one. The "problem" for a newbie like is me that the checkboxes will be asymmetrically located. They are in cells D14, D30, J14, J32, P14 and P30. I also need to duplicate this sheet a number of times, so preferably the script should work on any of the duplicates I make the same way. The cells will always be these six cells in every sheet.
I've seen many posts about this subject and tried to use the example scripts to my problem without success, so I want to ask specifically about my spreadsheet.
Here is a link to a draft version of my sheet with the locations of the checkboxes:
https://docs.google.com/spreadsheets/d/1PtxetS-Gyv7LyMD19yF3NuWZ24RvqUjyK42SmQTttiU/edit?usp=sharing
Thank you,
Lassi
You can set up an onEdit() trigger that will:
Check whether the cell edited is a checkbox (by verifying its range).
Check that the new value for the cell is TRUE (the checkbox has been enabled).
In case both of 1 and 2 apply, unset the rest of the defined checkboxes in the same sheet.
Note that the execution of the script may be a bit slow (due to Google Apps Script limitations) and that the script will work independently for each of the Sheets in your workbook.
Code
var CHECKBOX_CELLS = ["D14", "J14", "P14", "D30", "J32", "P30"];
function onEdit(e) {
var range = e.range;
var checkboxIndex = CHECKBOX_CELLS.indexOf(range.getA1Notation());
if (checkboxIndex > -1 && range.getValue() == true) {
var sheet = range.getSheet();
for (var i=0; i<CHECKBOX_CELLS.length; i++) {
if (i==checkboxIndex) continue;
sheet.getRange(CHECKBOX_CELLS[i]).setValue(false);
}
}
}

Best practices to execute faster a CasperJS script that scrapes thousands of pages

I've written a CasperJS script that works very well except that it takes a (very very) long time to scrape pages.
In a nutshell, here's the pseudo code:
my functions to scrape the elements
my casper.start() to start the navigation and log in
casper.then() where I loop through an array and store my links
casper.thenOpen() to open each link and call my functions to scrap.
It works perfectly (and fast enough) for scraping a bunch of links. But when it comes to thousands (right now I'm running the script with an array of 100K links), the execution time is endless: the first 10K links have been scrapped in 3h54m10s and the following 10K in 2h18m27s.
I can explain a little bit the difference between the two 10K batches : the first includes the looping & storage of the array with the 100K links. From this point, the scripts only open pages to scrap them. However, I noticed the array was ready to go after roughly 30 minutes so it doesn't explain exactly the time gap.
I've placed my casper.thenOpen() in the for loop hoping that after each new link built and stored in the array, the scrapping will happen. Now, I'm sure I've failed this but will it change anything in terms of performance ?
That's the only lead I have in mind right now and I'd be very thankful if anyone is willing to share his/her best practices to reduce significantly the running time of the script's execution (shouldn't be hard!).
EDIT #1
Here's my code below:
var casper = require('casper').create();
var fs = require('fs');
// This array maintains a list of links to each HOL profile
// Example of a valid URL: https://myurl.com/list/74832
var root = 'https://myurl.com/list/';
var end = 0;
var limit = 100000;
var scrapedRows = [];
// Returns the selector element property if the selector exists but otherwise returns defaultValue
function querySelectorGet(selector, property, defaultValue) {
var item = document.querySelector(selector);
item = item ? item[property] : defaultValue;
return item;
}
// Scraping function
function scrapDetails(querySelectorGet) {
var info1 = querySelectorGet("div.classA h1", 'innerHTML', 'N/A').trim()
var info2 = querySelectorGet("a.classB span", 'innerHTML', 'N/A').trim()
var info3 = querySelectorGet("a.classC span", 'innerHTML', 'N/A').trim()
//For scraping different texts of the same kind (i.e: comments from users)
var commentsTags = document.querySelectorAll('div.classComments');
var comments = Array.prototype.map.call(commentsTags, function(e) {
return e.innerText;
})
// Return all the rest of the information as a JSON string
return {
info1: info1,
info2: info2,
info3: info3,
// There is no fixed number of comments & answers so we join them with a semicolon
comments : comments.join(' ; ')
};
}
casper.start('http://myurl.com/login', function() {
this.sendKeys('#username', 'username', {keepFocus: true});
this.sendKeys('#password', 'password', {keepFocus: true});
this.sendKeys('#password', casper.page.event.key.Enter, {keepFocus: true});
// Logged In
this.wait(3000,function(){
//Verify connection by printing welcome page's title
this.echo( 'Opened main site titled: ' + this.getTitle());
});
});
casper.then( function() {
//Quick summary
this.echo('# of links : ' + limit);
this.echo('scraping links ...')
for (var i = 0; i < limit; i++) {
// Building the urls to visit
var link = root + end;
// Visiting pages...
casper.thenOpen(link).then(function() {
// We pass the querySelectorGet method to use it within the webpage context
var row = this.evaluate(scrapDetails, querySelectorGet);
scrapedRows.push(row);
// Stats display
this.echo('Scraped row ' + scrapedRows.length + ' of ' + limit);
});
end++;
}
});
casper.then(function() {
fs.write('infos.json', JSON.stringify(scrapedRows), 'w')
});
casper.run( function() {
casper.exit();
});
At this point I probably have more questions than answers but let's try.
Is there a particular reason why you're using CasperJS and not Curl for example ? I can understand the need for CasperJS if you are going to scrape a site that uses Javascript for example. Or you want to take screenshots. Otherwise I would probably use Curl along with a scripting language like PHP or Python and take advantage of the built-in DOM parsing functions.
And you can of course use dedicated scraping tools like Scrapy. There are quite a few tools available.
Then the 'obvious' question: do you really need to have arrays that large ? What you are trying to achieve is not clear, I am assuming you will want to store the extracted links to a database or something. Isn't it possible to split the process in small batches ?
One thing that should help is to allocate sufficient memory by declaring a fixed-size array ie:
var theArray = new Array(1000);
Resizing the array constantly is bound to cause performance issues. Every time new items are added to the array, expensive memory allocation operations must take place in the background, and are repeated as the loop is being run.
Since you are not showing any code, so we cannot suggest meaningful improvements, just generalities.

Copy range in google docs to another sheet

I have Google form that gets filled in by a few users. Works great but has it's limitations.
I'd like to copy the data entered from the active sheet to a new sheet called "work", all information except the first row that is. In the first row I have a few array formulas that populate some cells as new data is entered on the active sheet.
The second sheet (work) has a header row with all the formatting, data validation, some formulas etc (row 1). This information can not be applied when a new record is added via the form.. so I am told..
Thus, once the data has been copied from the active sheet (called active) I'd like the new data to be formatted as per the heading row (row 1) of the "work" sheet with all the formatting, validation, formulas etc being applied to the new data.
Is this doable? I am a noob when it comes to scripting so a complete solution would be highly appreciated.
here is a sample form you can play with
https://docs.google.com/spreadsheet/ccc?key=0AplugTacg-08dFNRUHROSW82bDhESkxBdjVTV0NOLUE
First thing i noticed one can not just copy/paste as the array formulas will bong things up so it has to be a paste special - values only
Any help greatly appreciated.
I'm struggling a bit with the logic behind what you're doing, but I've attempted a solution in sheet 'work2' in this copy of your spreadsheet. Perhaps have a play and report back what's not right or missing.
The script is this:
function onFormSubmit(e) {
var sheet = SpreadsheetApp.getActive().getSheetByName('work2');
var nextRow = sheet.getRange(sheet.getLastRow() + 1, 1, 1, 9);
sheet.getRange(2, 1, 1, 9).copyTo(nextRow);
var sLength = e.values[2].length;
var huuh = e.values[3] * sLength;
var pending = e.values[3] * e.values[3] / huuh;
var nextMonth = e.values[3] + pending;
nextRow.setValues([[e.values[0], e.values[1], huuh, e.values[2], sLength, e.values[3], 'Please select action', pending, nextMonth]]);
}
and has an "on form submit" trigger attached to it.

Resources