This question already has answers here:
Google Script version of VLookup (More Efficient Method?)
(2 answers)
Closed 2 years ago.
I need your help please. I would like to do a for loop or something else that works like a Formula =Vlookup
I have two Sheets. in Sheet1 (Overview) there are ID's like 1000, 1002, 1003,...,100X in Column A;
Sheet2 is a Form Response Sheet (Response), where you need to enter your ID and an Action with 'Ok' and 'Nok'. The ID I enter appears in Sheet2 Column B and the Action (Ok/Nok) apperas in Sheet2 Column C.
Now I would like to Copy the Ok/Nok to the Row with the same ID in the Overview sheet with a onFormSubmit function.
for Example. Person with ID 1005 makes a form response with the Action 'Ok'. Now should the 'Ok' copied to the Overview sheet in Column B and in the exact row (in this case: row with the ID 1005).
Here is my function. but I don't want to have formulars in the sheet. so I aked for another solution.
function vlookup() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var cell = sheet.getRange(1,5);
cell.setFormula('=ARRAYFORMULA(IFS(ROW(Response!B:B)=1,"Action from
User",Response!B:B="","",TRUE,IFERROR(VLOOKUP(A:A,Response!B:C,2,0),"Waiting for Response")))');
}
Hope someone can help me.
Thank you in advance for your help!
Jonas
Thank you for all these answers. I tryed the code from stackoverflow.com/questions/60255775 – TheMaster and that workes fine!
but it seams very complicated for a programming beginner. exspecially the part with the "Hash".
I also added a second compare and copy to get the data from a Reason if the Nok is used in the Form.
const ss = SpreadsheetApp.getActive();
/**
* #param {GoogleAppsScript.Spreadsheet.Sheet} fromSht -Sheet to import from
* #param {GoogleAppsScript.Spreadsheet.Sheet} toSht -Sheet to import to
* #param {Number} fromCompCol -Column number of fromSht to compare
* #param {Number} toCompCol -Column number of toSht to compare
* #param {Number} fromCol -Column number of fromSht to get result
* #param {Number} toCol -Column number of toSht to get result
*/
function copyToOverview(e,response,
fromSht = ss.getSheetByName('Response'),
toSht = ss.getSheetByName('Overview'),
fromCompCol = 2,
toCompCol = 1,
fromCol = 3,
toCol = 2,
fromColRej = 4,
toColRej = 3
) {
const toShtLr = toSht.getLastRow();
const toCompArr = toSht.getRange(2, toCompCol, toShtLr - 1, 1).getValues();
const fromArr = fromSht.getDataRange().getValues();
fromCompCol--;
fromCol--;
fromColRej--;
/*Create a hash object of fromSheet*/
const obj1 = fromArr.reduce((obj, row) => {
let el = row[fromCompCol];
el in obj ? null : (obj[el] = row[fromCol]);
return obj;
}, {});
/*Create a second hash object of fromSheet to copy the Reason why it is Nok (also from filling out the Form) */
const obj3 = fromArr.reduce((obj2, row) => {
let el1 = row[fromCompCol];
el1 in obj2 ? null : (obj2[el1] = row[fromColRej]);
return obj2;
}, {});
//Paste to column first toSht copy the "ok/nok" second toSht for the Reason why Nok
toSht
.getRange(2, toCol, toShtLr - 1, 1)
.setValues(toCompArr.map(row => (row[0] in obj1 ? [obj1[row[0]]] : [null])));
toSht
.getRange(2, toColRej, toShtLr - 1, 1)
.setValues(toCompArr.map(row => (row[0] in obj3 ? [obj3[row[0]]] : [null])));
}
I also tried the Code from "Michiel the Temp" and it seams, that it also works.
The code from "Mateo Randwolf" looks very simple and I tried it too. Works also very good!
I have modified it a bit and it works like I wish! I think I will use this code.
function onFormSubmit(e) {
// Get the sheet where the form responses are submitted and the one where we want to check the IDs
var formSheet = SpreadsheetApp.getActive().getSheetByName('Response');
var destinationSheet = SpreadsheetApp.getActive().getSheetByName('Overview');
// Get the new incoming data (ID and Ok/Nok) with each form submit by accessing
// the trigger object e which is the submited and new form response row
var submittedId = formSheet.getRange(e.range.getRow(), 2).getValue();
var submittedValue = formSheet.getRange(e.range.getRow(), 3).getValue();
var submittedValueReason = formSheet.getRange(e.range.getRow(), 4).getValue();
// get all the ID values we have in the sheet we want to check them. flat will convert all the returning
// 2D array of values in a 1D array with all the IDs
var idRange = destinationSheet.getRange(1, 1, destinationSheet.getLastRow(),1).getValues().flat();
// iterate over all your IDs
for(i=0;i<idRange.length;i++){
// if one ID is the same as the incoming one from the form response
if(idRange[i] == submittedId){
// set its value to the one submitted by the form
destinationSheet.getRange(i+1, 2).setValue(submittedValue);
}
if(idRange[i] == submittedId){
destinationSheet.getRange(i+1, 3).setValue(submittedValueReason);
destinationSheet.getRange(i+1, 2).getValue() == "Nok" ? destinationSheet.getRange(i+1, 4).setValue("Closed") : destinationSheet.getRange(i+1, 4).setValue("Open");
}
}
}
Thank you all for the Help you are amazing!
So I can do my next step in the Project with updating checkboxes in the Form.
I didn't test this with a trigger but this should work
function vlookup() {
var ssOverview = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Overview");
var ssOverviewLr = ssOverview.getLastRow();
var ssOverviewData = ssOverview.getRange(2, 1, ssOverviewLr, 1).getValues(); //assuming you have a header in the first row
var ssResponse = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Response");
var ssResponseLr = ssResponse.getLastRow();
var newResponse = ssResponse.getRange(ssResponseLr, 2, 1, 2).getValues();
var Ids = ssOverviewData.map(function (r){return r[0];});
for(var i = 0; i < newResponse.length; i++)
{
var row = newResponse[i];
var id = row[0];
var action = row[1];
var index = Ids.indexOf(id);
if(index == -1)
{
SpreadsheetApp.getActiveSpreadsheet().toast("No matches", "Be aware")
}
else
{
ssOverview.getRange(index + 2, 2).setValue(action); //this puts the action in column B
}
}
}
In order to check the IDs every time there is a new form submission and change the data in the ID sheet accordingly you will need to use installable triggers. Specifically you should use a FormSubmit trigger which triggers the function every time there is a form submission. Along with this trigger you will use its event object.
To add an installable trigger, in your Apps Script editor go to Edit -> Current project's triggers and create a new trigger by clicking Add trigger. Make sure that you select On form submit as the event type and that you select the function presented below (so please first copy/paste the function below before creating your trigger).
The following function takes use of this trigger event to compare the incoming data to your Column A of IDs and check for matches and if so it adds the relevant Ok/Nok information. It has self explanatory comments:
function onFormSubmit(e) {
// Get the sheet where the form responses are submitted and the one where we want to check the IDs
var formSheet = SpreadsheetApp.getActive().getSheetByName('Form Responses 1');
var destinationSheet = SpreadsheetApp.getActive().getSheetByName('Check');
// Get the new incoming data (ID and Ok/Nok) with each form submit by accessing
// the trigger object e which is the submited and new form response row
var submittedId = formSheet.getRange(e.range.getRow(), 2).getValue();
var submittedValue = formSheet.getRange(e.range.getRow(), 3).getValue();
// get all the ID values we have in the sheet we want to check them. flat will convert all the returning
// 2D array of values in a 1D array with all the IDs
var idRange = destinationSheet.getRange(1, 1, destinationSheet.getLastRow(),1).getValues().flat();
// iterate over all your IDs
for(i=0;i<idRange.length;i++){
// if one ID is the same as the incoming one from the form response
if(idRange[i] == submittedId){
// set its value to the one submitted by the form
destinationSheet.getRange(i+1, 2).setValue(submittedValue);
}
}
}
I have a table that displays several entries, each has an <input>. The user can dynamically add additional inputs by clicking an "add entry" button. I need to iterate over them before saving and validate each one. I simplified my example to check that the value of each input is greater than 100 (ultimately I will use a pattern-match to validate MAC and IP addresses).
I can probably handle it if I could select all <input>s, but I would really like to select a specific <input> using an index I already have in my scope. I read that angular.element is a way, but I need to select something that was dynamically created, and thus not named something easy like id="myInput". Unless I use an id of "input" and append a unique number with Angular's $index in the id attribute?
Here is my Fiddle that shows what I'm doing. Line 44 is an if() that should check if any <input> is greater than 100. The "Save Row" button validates that the input is greater than 100, but if you edit a line, I need the "Save" button to validate any that the user has edited (by clicking Edit next to it).
tl;dr:
How can I use Angular to select an <input> that has been created dynamically?
I have updated your fiddle in a clean way so that you can maintain the validation in a generic method for both add & edit.
function validateBinding(binding) {
// Have your pattern-match validation here to validate MAC and IP addresses
return binding.ip > 100;
}
Updated fiddle:
https://jsfiddle.net/balasuar/by0tg92m/27/
Also, I have fixed the current issue with editing you have to allow multiple editing without save the first row when clicking the next edit on next row.
The validation of 'save everything' is now cleaner in angular way as below.
$scope.changeEdit = function(binding) {
binding.onEdit = true;
//$scope.editNum = newNum;
$scope.showSave = true;
};
$scope.saveEverything = function() {
var error = false;
angular.forEach($scope.macbindings, function(binding) {
if(binding.onEdit) {
if (validateBinding(binding)) {
binding.onEdit = false;
} else {
error = true;
}
}
});
if (error) {
alert("One/some of the value you are editing need to be greater than 100");
} else {
$scope.showSave = false;
}
}
You can check the updated fiddle for the same,
https://jsfiddle.net/balasuar/by0tg92m/27/
Note: As you are using angular, you can validate the model as above and no need to retrieve and loop the input elements for the validation. Also for your case, validating the model is sufficient.
If you need some advanced validation, you should create a custom
directive. Since, playing around with the elements inside the
controller is not recommended in AngularJS.
You can use a custom class for those inputs you want to validate. Then you can select all those inputs with that class and validate them. See this Fiddle https://jsfiddle.net/lealceldeiro/L38f686s/5/
$scope.saveEverything = function() {
var inputs = document.getElementsByClassName('inputCtrl'); //inputCtrl is the class you use to select those input s you want to validate
$scope.totalInputs = inputs.length;
$scope.invalidCount = 0;
for (var i = 0; i < inputs.length; i++){
if(inputs[i].value.length < 100){
$scope.invalidCount++;
}
}
//do your stuff here
}
On line 46 a get all the inputs with class "classCtrl" and then I go through the input s array in order to check their length.
There you can check if any of them is actually invalid (by length or any other restriction)
http://plnkr.co/edit/r9hMZk?p=preview
I have a ui-grid where I have enabled multi selection. I want to be able to update the data whilst preserving the selection. If I just update the data then the selection is not preserved.
$scope.gridOpts.data = data2;
However I have defined a rowIdentity function, so that the id column uniquely identifies a row.
"rowIdentity" : function(row) {
return row.id;
}
Now if I select rows with id=Bob and id=Lorraine, then update the data, the rows are still selected. However the other fields in those rows are not updated.
How can I both preserve the selection and update all the data?
I think you need to keep track of you IDs yourself. So, you should remove the rowIdentifier, and instead add this piece at the beginning of your swapData function.
$scope.selIds = [];
for (var selRow of $scope.gridApi.selection.getSelectedRows()) {
$scope.selIds.push(selRow.id);
}
In addition to that, add an event handler on rowsRendered to re-select the previously selected rows
gridApi.core.on.rowsRendered($scope,function() {
for (var selId of $scope.selIds) {
for (var row of $scope.gridOpts.data) {
if (selId == row.id) {
$scope.gridApi.selection.selectRow(row);
}
}
}
});
You can put this in your registerApi callback.
I have a Gridview on my page and I'm using buffered store. Is there a way to get the visible number of row count. Thank you
Here is a sample code that you can try: (I hope you'll get some idea from this)
// The below condition has to be checked for each record
// record: record instance
var me = this; // grid scope
Ext.Array.each(me.columns, function (item) { // iterate through each column in the grid
if (item.hidden || !item.dataIndex) { // you can avoid hidden columns and one's that re not bound to the store
return;
}
var cellVal;
try {
cellVal = Ext.fly( me.view.getCell(record, item)).select('cell selector class').elements[0].innerHTML;
} catch (e) {
// handle if you want
}
if (!Ext.isEmpty(cellVal)) {
// this record has been rendered
}
}, this);
This will get you all the records that are rendered. Since you are using a bufferedRenderer, this will also return the records that are rendered but not in the view, you can check and put an offset for the buffer.
Note: I've a similar logic in working in ExtJs 5 but haven't tested in touch.
I am attemping to get the last row of a ng-repeat(ed) table via protractor to test and ensure the object I just created in a previous test run shows up. I have gotten as far as getting all of the text of the row but cannot seem to figure out through trial and error how to get each column of the last row as part of the array so I can verify each piece and then in the last column I have buttons to click which will be the next step.
The code I have so far is:
var elems = element.all(by.repeater('alert in alerts'));
elems.last().then(function(elm) {
console.log(expect(elm.getText()).toMatch('/testRunner/'));
});
As mentioned above, the expected output/output I want to check against is 'textRunner' and instead I get the entire row of text as such:
testRunner testing the runner 5 minutes No View Edit Enable
EDIT
Here is my final code:
var rows = element.all(by.repeater('alert in alerts'));
rows.last().then(function(row) {
var rowElems = row.findElements(by.tagName('td'));
rowElems.then(function(cols){
expect(cols[0].getText()).toContain('testRunner');
expect(cols[1].getText()).toContain('testing the runner');
expect(cols[4].getText()).toContain('5 minutes');
});
});
var rows = element.all(by.repeater('alert in alerts'));
rows.last().then(function(row) {
var rowElems = row.findElements(by.tagName('td'));
rowElems.then(function(cols){
expect(cols[0].getText()).toContain('testRunner');
expect(cols[1].getText()).toContain('testing the runner');
expect(cols[4].getText()).toContain('5 minutes');
});
});
Assuming your are using a <table>, and your different values are in <td>, you could check values by using the by.tagName locator strategy.
var rows = element.all(by.repeater('alert in alerts'));
var row = rows.last();
var rowElems = row. findElements(by.tagName('td'));
expect(rowElems.get(0).getText()).toMatch('/testRunner/');
expect(rowElems.get(1).getText()).toMatch('/testing/');
expect(rowElems.get(2).getText()).toMatch('/the/');
expect(rowElems.get(3).getText()).toMatch('/runner/');
expect(rowElems.get(4).getText()).toMatch('/5/');
expect(rowElems.get(5).getText()).toMatch('/minutes/');
expect(rowElems.get(6).getText()).toMatch('/No/');
expect(rowElems.get(7).getText()).toMatch('/View/');
expect(rowElems.get(8).getText()).toMatch('/Edit/');
expect(rowElems.get(9).getText()).toMatch('/Enable/');