validating google form before "submit" - validation

I'm looking for a way to validate a form "before" submitting it.
I need to:
a) get total value of field A : from linked Response Sheet. If total value > 0 then proceed to step b. If not, then "deactivate" field A and proceed to step c.
b) FORM'S field A can receive input.
c) if not, choose another field (B, C...or F)
... and so on.
Can this be done, without having to SUBMIT (which will close it) and being sent to the RESPONSE SHEEET? I want to stay in the FORM until final approval of input by user and validation scripts.
my wip form
Below some literature I am reading at the moment.
// Open a form by ID and log the responses to each question.
var form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
var formResponses = form.getResponses();
for (var i = 0; i < formResponses.length; i++) {
var formResponse = formResponses[i];
var itemResponses = formResponse.getItemResponses();
for (var j = 0; j < itemResponses.length; j++) {
var itemResponse = itemResponses[j];
Logger.log('Response #%s to the question "%s" was "%s"',
(i + 1).toString(),
itemResponse.getItem().getTitle(),
itemResponse.getResponse());
}
}
google developers's forms reference:

Google Forms include a built-in way to validate some questions types. We could programmatically add validations to supported question types by using Class TextValidationBuilder but we can't use JavaScript to make validations before the response is submitted.
After the response is submitted we could use JavaScript to make validations but the results of them will not be shown on the form response view.

Related

How to store data for use in multiple data validation using GAS?

I have a script running on Google Sheets, which brings data from another spreadsheet/file as an array and sets one of its column's data as a data validation into a cell. Then, as the user picks one option of this data validation, the script goes back to that file and brings its related data and sets it in an adjacent column and this repeats about 3 times, making the process slow.
I was wondering if that would be possible to store the first data collection into the document property and set the data validations by grabbing related information from that data set, instead of going to the other file everytime.
Here's an update, with a working version:
function listaCategorias() {
let listaGeral = sheetBDCadProd.getRange(2, 1, sheetBDCadProd.getLastRow(), 45).getValues();//Gets all values
//Extracts a column of interest for this first data validation setting
let categorias = [];
for (let a = 0; a < listaGeral.length; a++) {
categorias.push(listaGeral[a][17])
}
let uniqueCat = [...new Set(categorias)]; //Gets a list of unique values. Not sure how I'd do that within new Set, so I did a for loop before
//Sets the data validation
const cell = sheetVendSobEnc.getRange('B5');
const validationCat = SpreadsheetApp.newDataValidation().requireValueInList(uniqueCat).setAllowInvalid(false).build();
cell.clearContent();
cell.clearDataValidations();
cell.setDataValidation(validationCat);
//Saves the data into the document property for usage in the next script/data validation
listaGeral = JSON.stringify(listaGeral)
PropertiesService.getDocumentProperties().setProperty('listaGeral', listaGeral);
}
//This is getting one of the columns, based on the option picked..the one generated by the data validation above.
function listaDescricao() {
const categoria = sheetVendSobEnc.getRange('B5').getValue();
const dadosCadProd = PropertiesService.getDocumentProperties().getProperty('listaGeral')
let cadGeral = JSON.parse(dadosCadProd);
//Filters the elements matching the option picked
let filteredNomeSobEnc = cadGeral.filter(function (o) { return o[17] === categoria });
//Filters unique values
let listToApply = filteredNomeSobEnc.map(function (o) { return o[7] }).sort().reverse();
let descUnica = listToApply.filter((v, i, a) => a.indexOf(v) === i);
Logger.log('Descrição Única: ' + descUnica)
}
It's working, but I'd like to know the rooms for improvement here.
Thanks.

How to get a list of links of a Wikipedia page (article) and the number of how many people clicked on a link?

This is my code so far. Now I have a list of how many people looked at the page (article) but I wondered if it's possible to make a list of links of a wikipedia page (article) and how many times there is clicked on the links?
String[] articles = {"Hitler", "SOA", "Albert_Einstein"};
void setup() {
for (int i = 0; i < articles.length; i++) {
String article = articles[i];
String start = "20160101"; // YYYYMMDD
String end = "20170101"; // YYYYMMDD
// documentation: https://wikimedia.org/api/rest_v1/?doc#!/Pageviews_data/get_metrics_pageviews_per_article_project_access_agent_article_granularity_start_end
String query = "http://wikimedia.org/api/rest_v1/metrics/pageviews/per-article/en.wikipedia/all-access/all-agents/"+article+"/daily/"+start+"/"+end;
JSONObject json = loadJSONObject(query);
JSONArray items = json.getJSONArray("items");
int totalviews = 0;
for (int j = 0; j < items.size(); j++) {
JSONObject item = items.getJSONObject(j);
int views = item.getInt("views");
totalviews += views;
}
println(article+" "+totalviews);
}
}
To get the links from an article use action=query in the API together with props=links.
In your example: https://en.wikipedia.org/w/api.php?action=query&format=json&prop=links&meta=&titles=Albert+Einstein%7CHitler%7CSOA&pllimit=500
Do note that this is not all results (you can only get 500 at a time) so you need to make more requests using the plcontinue you get as a parameter in the new request.
Break your problem down into smaller steps.
Create a single program that just returns all of the links on a wikipedia page. Make sure you have that program working perfectly, and post an MCVE if you get stuck.
Separately from that program, create a separate program that takes a hardcoded URL and returns the number of views that URL has. Again, post an MCVE if you get stuck. When you get that working, move up to a program that takes a hardcoded ArrayList of URLs and returns the pageviews for each URL.
Then when you have them both working separately, then you can start thinking about combining them.

Lotus Domino: View pagination on web

I read on many forums about how to implement a solution for view pagionation, but I didn't solve it.
I created $$ViewTemplateDefault containing some personalized hotspotbuttons for Next, Previous and a text field $$ViewBody. ( or, alternatively, an embedded view ).
Any tips and help will be really appreciated.
I will explain in a couple words, just to be clear:
So, initially: the first 30 lines will appear => in a right corner: Page 1.
If Next is clicked => the next 30 lines => Page 2. and so on.
Here is a working solution for categorized views too. It calculates the current page number based on the previous page number and uses cookies.
Add to your form a Path-Thru HTML text <span id="pageNumber"></span > for the page number:
and add following code to form's onLoad event as Web/JavaScript:
function getURLParameter(parameter) {
return decodeURIComponent((new RegExp('[?|&]' + parameter + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [, ""])[1].replace(/\+/g, '%20')) || null;
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return "";
}
function compareStart(start1, start2) {
var list1 = start1.split(".");
var list2 = start2.split(".");
for (i=0; i <100; i++) {
var value1 = list1[i];
var value2 = list2[i];
if (value1 == null) {
return value2 == null ? 0 : -1;
} else if (value2 == null) {
return 1;
}
value1 = Math.round(value1);
value2 = Math.round(value2);
if (value1 !== value2) {
return value1 < value2 ? -1 : 1;
}
}
}
var start = getURLParameter("Start");
var page = "1";
if (start == null || start === "1") {
window.name = Math.floor((Math.random()*10000)+1);
start = "1";
} else {
page = getCookie("page" + window.name);
var oldStart = getCookie("start" + window.name);
page = Math.round(page) + compareStart(start, oldStart);
}
document.getElementById('pageNumber').innerHTML = page;
document.cookie = "page" + window.name + "=" + page;
document.cookie = "start" + window.name + "=" + start;
How does it work?
The commands #DbCommand("Domino"; "ViewNextPage") and #DbCommand("Domino"; "ViewPreviousPage") return an URL with parameter "&Start=". This is the row number in view where the current page starts. For categorized views they return a hierarchical number like "&Start=1.2.4.2". That means that the view starts at the first main topic, subtopic 2, subsubtopic 4, document 2.
This parameter "&Start=" gives us the possibility to recognize if user pressed "prev" or "next": we just compare the URL "&Start=" parameter of current and former page.
For that, we have to remember the URL "&Start=" parameter and put it into a cookie "start".
We also need to save the current page number. We put it into a cookie "page".
At onload event we calculate the current page number based on previous page number:
if "&Start=" parameter is larger now then we add 1
if "&Start=" parameter is smaller now then we subtract 1
if "&Start=" parameter didn't change then we keep the former value
If "&Start=" parameter is empty we know we are on page 1.
Here is one other thing we have to deal with: cookies are saved per user session not per browser tab. That means, if we have two views open in browser same cookies "start" and "page" would be used. To avoid that, we have to add to cookie name something tab specific. I use for that a random four digit number and save it in window.name which is tab specific.
I understand your question that you have a working form $$ViewTemplateDefault and now looking for a possibility to show the current page number "Page nn" in that form.
I assume that you use #DbCommand("Domino"; "ViewNextPage") for getting next page and #DbCommand("Domino"; "ViewPreviousPage") for getting previous page.
Those next and prev functions working the way that always one document will "overlap". If you have 30 lines per page and click next, then last document will be first in next page and next 29 show up in addition. You can watch that in used URL parameter "&Start=": 1 ... 30 ... 59 ... 88 ...
Knowing this you can count the current page number this way:
_start := #ToNumber(#Replace(#UrlQueryString("start"); ""; "1"));
_count := #ToNumber(#Replace(#UrlQueryString("count"); ""; "30")) - 1;
#Integer((#ToNumber(_start) / _count) + 1)
Be aware that this will work for non-categorized and non-collapsible views only.
A more sophisticated solution you can find here. It has additional features like GoTo page and Documents per page.
If you have the chance for your project then use XPages instead. You can do pagination much easier as it is available "out of the box".
Update:
You won't find a reasonable solution for categorized views. If you don't want to use Domino Data/Access Services REST API you have to live with the Domino view URL parameters (look here for "OpenView"). You aren't able to tell from "&Start=" or any other parameter on which page you are currently on.
The easiest way to get a good working pagination is using XPages. Hope you are allowed to use it in your project...

Angular.js - Data from AJAX request as a ng-repeat collection

In my web app i'm reciving data every 3-4 seconds from an AJAX call to API like this:
$http.get('api/invoice/collecting').success(function(data) {
$scope.invoices = data
}
Then displaying the data, like this: http://jsfiddle.net/geUe2/1/
The problem is that every time i do $scope.invoices = data ng-repeat rebuilds the DOM area which is presented in the jsfiddle, and i lose all <input> values.
I've tried to do:
angular.extend()
deep version of jQuery.extend
some other merging\extending\deep copying functions
but they can't handle the situation like this:
On my client a have [invoice1, invoice2, invoice3] and server sends me [invoice1, invoice3]. So i need invoice2 to be deleted from the view.
What are the ways to solve this problem?
Check the ng-repeat docs Angular.js - Data from AJAX request as a ng-repeat collection
You could use track by option:
variable in expression track by tracking_expression – You can also provide an optional tracking function which can be used to associate the objects in the collection with the DOM elements. If no tracking function is specified the ng-repeat associates elements by identity in the collection. It is an error to have more than one tracking function to resolve to the same key. (This would mean that two distinct objects are mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, before specifying a tracking expression.
For example: item in items track by item.id is a typical pattern when the items come from the database. In this case the object identity does not matter. Two objects are considered equivalent as long as their id property is same.
You need to collect data from DOM when an update from the server arrives. Save whatever data is relevant (it could be only the input values) and don't forget to include the identifier for the data object, such as data._id. All of this should be saved in a temporary object such as $scope.oldInvoices.
Then after collecting it from DOM, re-update the DOM with the new data (the way you are doing right now) $scope.invoices = data.
Now, use underscore.js _.findWhere to locate if your data._id is present in the new data update, and if so - re-assign (you can use Angular.extend here) the data-value that you saved to the relevant invoice.
Came out, that #luacassus 's answer about track by option of ng-repeat directive was very helpful but didn't solve my problem. track by function was adding new invoices coming from server, but some problem with clearing inactive invoices occured.
So, this my solution of the problem:
function change(scope, newData) {
if (!scope.invoices) {
scope.invoices = [];
jQuery.extend(true, scope.invoices, newData)
}
// Search and update from server invoices that are presented in scope.invoices
for( var i = 0; i < scope.invoices.length; i++){
var isInvoiceFound = false;
for( var j = 0; j < newData.length; j++) {
if( scope.invoices[i] && scope.invoices[i].id && scope.invoices[i].id == newData[j].id ) {
isInvoiceFound = true;
jQuery.extend(true, scope.invoices[i], newData[j])
}
}
if( !isInvoiceFound ) scope.invoices.splice(i, 1);
}
// Search and add invoices that came form server, but are nor presented in scope.invoices
for( var j = 0; j < newData.length; j++){
var isInvoiceFound = false;
for( var i = 0; i < scope.invoices.length; i++) {
if( scope.invoices[i] && scope.invoices[i].id && scope.invoices[i].id == newData[j].id ) {
isInvoiceFound = true;
}
}
if( !isInvoiceFound ) scope.invoices.push(newData[j]);
}
}
In my web app i'm using jQuery's .extend() . There's some good alternative in lo-dash library.

Get query string in Google CSE v2

I am using Google CSE v2, and I need to get the query that the user entered. The problem is that it is ajax, and the query is not in the url.
Does anyone know a solution?
Thanks
First off, when you create the search box, you need to give it a 'gname' attribute so you can identify it in your javascript, like so:
<gcse:searchbox gname="storesearch"></gcse:searchbox>
<gcse:searchresults gname="storesearch"></gcse:searchresults>
Or, if you're using the html5 style tags (which you should unless you have a reason not to):
<div class="gcse-searchbox" data-gname="storesearch"></div>
<div class="gcse-searchresults" data-gname="storesearch"></div>
(Replace 'storesearch' with whatever name you want to use to identify this custom search.)
More info on that here: https://developers.google.com/custom-search/docs/element#supported_attributes
Then, you can access the custom search element and get the current query like so:
var cseElement = google.search.cse.element.getElement('storesearch'),
query = cseElement.getInputQuery();
or if you don't need the reference to the element anymore, obviously that could be combined into one line:
var query = google.search.cse.element.getElement('storesearch').getInputQuery();
The docs for that part are here: https://developers.google.com/custom-search/docs/element#cse-element
I know this is already answered correctly. But for those also looking for a simple JS function to achieve this, here you go. Pass it the name of the variable you want to extract from the query string.
var qs = (function(a) {
if (a == "") return {};
var b = {};
for (var i = 0; i < a.length; ++i) {
var p=a[i].split('=');
if (p.length != 2) continue;
b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
}
return b;
})(window.location.search.substr(1).split('&'));

Resources