My goal is to pass in a YouTube channel's id and call that channel's video view metrics for a given window of time, store that data in an array and then pass each video view metric into one column of array.length in descending order.
//...key variables
var channelId = sheet.getActiveCell().getValue();
var offsetCell = sheet.getActiveCell().offset(0,2);
//further down the script...
for(i=0; i < dataResponse.items.length; i++) {
var data = [];
var vidDataLen = dataResponse.items.length;
var offsetRange = sheet.getRange(offsetCell, vidDataLen, 0)
data.push(dataResponse.items[i].statistics.viewCount);
//sheet.appendRow(data)
offsetCell.setValue([data])
}
As you can follow, I initially used appendRow, but I didn't want data to be appended into overwriting rows, I wanted the data to pass into, the column and row of offsetCell, and descending into the succeeding cells.
My efforts with
var offsetRange = sheet.getRange(offsetCell, vidDataLen, 0)
were attempting to create an active range based on the number of items in the data array, since that will vary on each channel.
However, that did not work, because getRange does not accept objects as parameters.
Are there any google-script functions I could use to pass the array data into an offset cell and successive cells in that same column?
NOTE: Running this script with the offsetRange variable commented out results in the YouTube api being successfully called, however, only one value from the 'data' array is passed into offsetCell
NOTE 2: I have scoured StackOverflow for google scripted questions and related subjects, however I have not seen an active or relevant solution for passing multiple stored values from an API call into a specific range of offset cells (in one column).
The solution for this was relatively straight forward
I removed the data array and used the statement sheet.insertRows(offsetCell.getRow()+1,vidDataLen);
to create the range of cells in the column I wanted to pass data into so existing data would not be overwritten
var vidDataLen = dataResponse.items.length;
sheet.insertRows(offsetCell.getRow()+1,vidDataLen);
for(i=0; i < dataResponse.items.length; i++) {
offsetCell.setValue(dataResponse.items[i].statistics.viewCount);
offsetCell = offsetCell.offset(1, 0);
}
Instead of passing data into a loop array, I just passed each value directly into the setValue() method's argument, and then assigned offsetCell to offsetting each new iteration of offsetCell by 1 row and 0 columns.
Related
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.
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);
}
}
}
I have created many, let's say three ObjectStores inside a defined and versioned IndexedDB schema.
I need to populate all of them. To do so, I created an object which stores both name end endpoint (where it gets data to populate).
Also to avoid error when trying to fill objectstores already populated, I use the count() method to ... count key inside the objectstore and if there are 0 key, then populates, else reads.
It works perfect if I execute on a one by one basis, that is instead of using a loop, declare and execute each one of the three objectstores.
However when invoking the function to populate each storage inside a loop, I get the following error message for the last two objectstores to be populated:
Failed to read the 'result' property from 'IDBRequest': The request
has not finished. at IDBRequest.counts.(anonymous function).onsuccess
Here is the code:
// object contains oject stores and endpoints.
const stores = [
{osName:'user-1', osEndPoint:'/api/1,
{osName:'user-2', osEndPoint:'/api/2},
{osName:'user-3', osEndPoint:'/api/3}
];
// open db.
var request = indexedDB.open(DB_NAME, DB_VERSION);
// in order to dynamically create vars, instantiate two arrays.
var tx = [];
var counts = [];
var total = [];
// onsuccess callback.
request.onsuccess = function (e) {
db = this.result;
for(k in stores) {
tx[k] = db.transaction(stores[k].osName).objectStore(stores[k].osName);
counts[k] = tx[i].count();
counts[k].onsuccess = function(e) {
total[k] = e.target.result;
// if the counting result equals 0, then populate by calling a function that does so.
if (total[k] == 0) {
fetchGet2(stores[k].osEndPoint, popTable, stores[k].osName); //
} else {
readData(DB_NAME, DB_VERSION, stores[0].osName);
}
};
} // closes for loop
}; // closes request.onsuccess.
The fetchGet2 function works well inside a loop, for example the loop used to create the objectstores, and also has been tested on a one by one basis.
It looks like an async issue, however I cannot figure how to fix the problem which is to be able to populate existing objectstores dynamically, avoiding to populate filled objectsores and only filling empty ones.
Indeed testing without the count issue, but inside the loop works perfect, or with the count but without loop.
At the moment and when logging counts[k], It only logs data for the last member of the object.
Thanks in advance, I'm coding with vanilla js, and I'm not interested in using any framework at all.
Yes, this looks like an issue with async. For loops iterate synchronously. Try writing a loop that does not advance i until each request completes.
I need is to dynamically add the points I’ve received from a forex provider to s Shield UI Chart. According to the documentation, there isn’t the possibility for dynamically adding of points At least there is no such method, something like: AddPoint or similar.
How can I still achieve a web page using Shield UI Chart, which to constantly show a couple of exchange rates?
You are right, that there is no addPoints method of the Shield UI Chart. However we can add the incoming data values to an array instead. You might find useful following code:
We need some arrays- as many as we need to show.
var EURUSD = new Array();
var USDCAD = new Array();
var GBPUSD = new Array();
In the body of the function, that will actually display the data we will have the following code:
EURUSD[EURUSD.length] = parseFloat(data.ticks.EURUSD);
USDCAD[USDCAD.length] = parseFloat(data.ticks.USDCAD);
GBPUSD[GBPUSD.length] = parseFloat(data.ticks.GBPUSD);
it will actually put the new data to the designated arrays. You can note, that each time data is received, it has been added to the last index of each array:
EURUSD.length
Since we don’t want our arrays to grow too large, it is good to specify how many points we need to keep. Once that limit is reached, we remove the oldest point:
if (EURUSD.length > 50)
EURUSD = EURUSD.splice(1, 49);
if (USDCAD.length > 50)
USDCAD = USDCAD.splice(1, 49);
if (GBPUSD.length > 50)
GBPUSD = GBPUSD.splice(1, 49);
At the end we need to recreate the chart, referencing the appropriate container:
var containter = $("#EURUSDChart").swidget();
containter.destroy();
and than create the chart again.
I'm using a parent to pass a multi-dimensional array to a child. Structure of the array, named projectPositions is as follows (with example data):
projectPositions[0][0] = 1;
projectPositions[0][1] = 5;
projectPositions[0][2] = '1AD';
projectPositions[0][3] = 'User name';
I need to take this inherited array and turn it into an arrayCollection so that I can use it as a dataProvider. Currently, my init function (which runs onCreationComplete) has this code in it to handle this task of array -> arrayCollection:
for (var i:int = 0; i < projectPositions.length; i++)
{
tempObject = new Object;
tempObject.startOffset = projectPositions[i][0];
tempObject.numDays = projectPositions[i][1];
tempObject.role = projectPositions[i][2];
tempObject.student = projectPositions[i][3];
positionsAC.addItemAt(tempObject, positionsAC.length);
}
Then, during a repeater, I use positionsAC as the dataprovider and reference the items in the following way:
<mx:Repeater id="indPositions" dataProvider="{positionsAC}" startingIndex="0" count="{projectPositions.length}">
<components:block id="thisBlock" offSet="{indPositions.currentItem.startOffset}" numDays="{indPositions.currentItem.numDays}" position="{indPositions.currentItem.role}" sName="{indPositions.currentItem.student}" />
</mx:Repeater>
This all works fine and returns the desired effect, but the load time of this application is around 10 seconds. I'm 99% sure that the load time is caused by the array -> arrayCollection for loop. Is there an easier way to achieve the desired effect without having to wait so long for the page to load?
The issue your having loading items could be because you are using a repeater instead of a list class.
With a repeater, there will be a block created in memory, and drawn on the screen. So, if you have 100 items in your array, then 100 blocks will be created. this could slow down both initial creation and the overall app.
A list based class focuses on a technique called renderer recycling; which means only the displayed elements are created and rendered on the screen. So, depending on settings, you'd usually have 7-10 'block' instances on the screen, no matter how many items you have in your array.
change
positionsAC.addItemAt(tempObject, positionsAC.length);
to
positionsAC.addItem(tempObject);
addItemAt is causing a reindex of the collection which can greatly slow down the collection.
[EDIT]
Put this trace statement before and after the loop
take the output and subtract one from the other and that will show how many milliseconds the loop has run.
var date:Date = new Date( );
trace( date.getTime())