Validate Spreadsheet in a HTML Service - javascript

I have an html form I created with HTML Service in Google Sheets. The form has three fields in it. I want to check if the data they entered in one of the fields is contained in a column of sheet2. If it is not, I just want to display an alert box saying "invalid input". Whats the best way to do this? This is my submit form. The line of code that compares the value to "test", is where I want to check against a value in the spreadsheet.
function formSubmit() {
if (document.getElementById("sku").value === "test") {
alert(document.getElementById("sku").value);
} else {
google.script.run.appendRowstoSheet(document.forms[0]);
document.getElementById("sku").value = ' ';
document.getElementById("sku").focus();
}
}
gs Code:
//insertValuestoSheet
function appendRowstoSheet(form){
var sku = form.sku,
loc_array = [{}];
location = form.location,
reference = form.reference
loc_array = location.split("-");
sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//validateMySheet();
//don't insert blank sku's
//Also want to have it so it doesn't insert values where if the sku
//doesn't match a sku in sheet2, column A. Alert the user if this condition
if (sku != " ") {
sheet.appendRow(["'"+reference," "," "," ","'"+sku, "1", " ", " ", " ", "'"+loc_array[0],"'"+loc_array[1],"'" + loc_array[2],"'"+loc_array[3]]);
}
}
I modified my code with a better function name and added the function. Currently in appendRowstoSheet, it inserts into the spreadsheet. How do I return a failure here if my condition isn't met. Am I understanding that correctly?

You need to return something:
if (sku != " ") {
sheet.appendRow(["'"+reference," "," "," ","'"+sku, "1", " ", " ", " ", "'"+loc_array[0],"'"+loc_array[1],"'" + loc_array[2],"'"+loc_array[3]]);
return true;
} else {
return false;
};
Note the use of return true; and return false;. Either true or false will be returned to your HTML code and passed to the success handler.
You need to run a withSuccessHandler() and do the final processing in that separate "success" function. First get the data out of the spreadsheet. Then if that is successful, a different function will run that compares the values.
<script>
function onSuccess(argReturnValue) {
Console.log('argReturnValue: ' + argReturnValue);
if (argReturnValue === true) {
//Code here
} else {
};
//Note that the argument: argValuesFromSpreadsheet is the return value from
//the gs script function, "getValuesFromForm"
var valueFromForm = document.getElementById("sku").value;
//You'll need to modify this line to get a specific value
var valueFromSpreadsheet = argValuesFromSpreadsheet[0];
Logger.log('valueFromSpreadsheet: ' + valueFromSpreadsheet);
if (valueFromForm === valueFromSpreadsheet) {
alert('alert text here: ' + document.getElementById("sku").value);
} else {
document.getElementById("sku").value = ' ';
document.getElementById("sku").focus();
}
}
function formSubmit() {
google.script.run
.withSuccessHandler(onSuccess)
.getValuesFromForm(document.forms[0]);
};
</script>
Note: You will need to make some modifications to the code, depending on what data, and the format of the data being returned from the spreadsheet:
var valueFromSpreadsheet = argValuesFromSpreadsheet[0];
That line needs to be modified.

Related

Creating record using JS and JSON not correctly using owner value

I'm attempt to create a record in Dynamics 365 using JavaScript however the Owner field is not being set properly. The record creates just fine if I remove the setting of the 'ownerid' field. I have also tried formatting the guid both in lowercase and uppercase with no success (see comments in code). The fields are displayed as expected in the alert.
When the script is run both with the code that makes the guid lowercase or not, I get the following error:
Error: An error occurred while validating input paramters: Microsoft.OData.ODataException: A node of type 'StartArray' was read from the JSON reader when trying to read the contents of the property 'ownerid'; however, a 'StartObject' node or 'PrimitiveValue' node with null value was expected.
var managingDirector = Xrm.Page.getAttribute("new_managingdirector").getValue();
var md_id = managingDirector[0].id;
var md_name = managingDirector[0].name
var md_entityType = "systemuser"
//md_id = md_id.replace(/[{}]/g,"");
//md_id = md_id.toLowerCase();
//md_id = "{" + md_id + "}";
if (managingDirector != null) {
console.log(managingDirector[0]);
alert("MD is " + md_name + " with id " + md_id + " and type " + md_entityType);
} else {
alert("MD is null");
}
var md_owner = new Array();
md_owner[0] = new Object();
md_owner[0].name = md_name;
md_owner[0].id = md_id;
md_owner[0].entityType = md_entityType;
var data =
{
"new_name": "Sample Practice Management",
"new_totalamountdue": amountDue,
"new_deductions": deductionAmount,
"new_deductionsnotes": deductionNotes,
"ownerid": md_owner
}
// create pm record
Xrm.WebApi.createRecord("new_practicemanagement", data).then(
function success(result) {
alert("Practice Management record created with ID: " + result.id);
// perform operations on record creation
},
function (error) {
alert("Error: " + error.message);
// handle error conditions
}
);
When I attempt to restructure the data variable like this (with both lowercase and uppercase ID)
var data =
{
"new_name": "Sample Practice Management",
"new_totalamountdue": amountDue,
"new_deductions": deductionAmount,
"new_deductionsnotes": deductionNotes,
"ownerid": {
name: md_name,
id: md_id,
entityType: md_entityType
}
}
I get the following error:
An error occurred while validating input paramters: Microsoft.OData.ODataException: Does not support untyped vvalue in non-open type.
When I see your code you have data i.e field and it's value as below
var data =
{
"new_name": "Sample Practice Management",
"new_totalamountdue": amountDue,
"new_deductions": deductionAmount,
"new_deductionsnotes": deductionNotes,
"ownerid": md_owner
}
Now if you look at my code owner id is set as
entity["ownerid#odata.bind"] = "/systemusers(58127B9D-AFBC-E811-A958-000D3AB42BE8)";
Below is the code which worked for me, I just tried creating contact record.
var entity = {};
entity.firstname = "Webapi1";
entity["ownerid#odata.bind"] = "/systemusers(58127B9D-AFBC-E811-A958-000D3AB42BE8)";
Xrm.WebApi.online.createRecord("contact", entity).then(
function success(result) {
var newEntityId = result.id;
},
function(error) {
Xrm.Utility.alertDialog(error.message);
}
);
To make your life easier w.r.t developement try CRMRESTBuilder you will find most of your code auto generated here.

JQuery $().each exiting too early

I'm using the function below to validate several input fields on a web form. The function iterates through the fields using jQuery .each() iteration. The ValidateInput function validates each individal input field, returning a true or false.
function ValidateForm() {
var result = true;
$('[data-regex]').each(function() {
result = result && ValidateInput(this);
// return true;
});
if ( result == false ) {
$(".alert").show();
}
return result;
}
The problem I'm having is that the .each() is terminating early, as soon as any individual input field fails validation. I know that if function() returns false, the .each() will terminate, but I don't see how I'm doing that. I've even tried adding an explicit return true; as the last line of function(), but this made no difference.
For completeness, here's ValidateInput:
function ValidateInput(thisInput) {
var fieldName = $(thisInput).attr('name');
var fieldValue = $(thisInput).val();
var regex = $(thisInput).attr('data-regex');
console.log('validating field "' + fieldName + '" value "' + fieldValue + '" with regEx "' + regex + '"';
var re = new RegExp(regex);
var result = re.test(fieldValue);
if ( result ) {
console.log('- passed');
$(thisInput.closest(".form-group")).addClass("has-success").addClass("has-feedback")
} else {
console.log('- failed');
$(thisInput.closest(".form-group")).addClass("has-error").addClass("has-feedback")
};
return result;
}
The code that calls ValidateForm is as follows:
<input type="submit" name="ct108" value="Save User" onclick="return ValidateForm();" />
What am I missing?
Your ValidateInput method stops processing after the first one that doesn't pass validation because this line:
result = result && ValidateInput(this);
The way && works is that if the item on the left side is truthy the value on the right is returned as the value, otherwise the value on the left is returned as the value.
You can guarantee that each one is processed by flipping it around:
result = ValidateInput(this) && result;

Multiple records are added to the database

I'm puzzling over a weird problem I cannot replicate.
Scenario
I wrote a simple nodejs application that, after some UI interaction append some records to an Access 97 database. I'm using node-adodb to connect with it.
Some relevant piece of code.
var DBDATA = require('node-adodb'), dbConnection = DBDATA.open('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=/path/to/my/file.mdb');
function append(data, callback)
{
var table;
var query;
var array = [];
array.push({name: "Date", value: formatDate(data.date)});
array.push({name: "Time", value: formatTime(data.date)});
array.push({name: "Type", value: Number(data.Type)});
array.push({name: "Value", value: Number(exists(data.value, 0))});
// ...other fields
var fields = array.map(function (e) {
return "[" + e.name + "]";
}).join(",");
var values = array.map(function (e) {
return e.value;
}).join(",");
table = "tblData";
query = 'INSERT INTO ' + table + '(' + fields + ') ' + 'VALUES (' + values + ')';
dbConnection
.execute(query)
.on('done', function (data) {
return callback({id: id, success: true});
})
.on('fail', function (data) {
console.log(data);
return callback({id: id, success: false});
});
}
The issue
The above function is called whenever a new record is ready. Usually it works fine, but it happens about 1 time per week (among hundreds of records) that I find in the database multiple rows identical.
Due to the nature of the information this is impossible - I mean, it's impossible that the actual data is the same.
I guessed for a bug in the caller, that for some reasons sends me the same variable's content. Hence I added a check before append the record.
What I tried to do
function checkDuplicate(table, array, callback)
{
var query = "SELECT * FROM " + table + " WHERE ";
array.forEach(function(element)
{
query += "([" + element.name + "]=" + element.value + ") AND ";
});
query = query.substr(0, query.length - 4);
dbConnection
.query(query)
.on("done", function (data) {
return callback(data.records.length > 0);
})
.on("fail", function (data) {
return callback(false);
});
}
in the append function I call this one and if it returns a value > 0 I don't execute the query, because it would mean there already is the same row.
Testing it with fake data gave good results: no multiple records were added.
Unfortunately, this didn't fixed the issue in the real world. After 20 days I noticed that a row was added three times.
Questions
Do you see any evidence of a major mistake in my approach?
Is there a more reliable way to avoid this problem?
Please note I cannot change the database structure because it's not mine.
UPDATE
This is the new code I'm using:
// Add only if there isn't an identical record
query = 'INSERT INTO ' + table + '(' + fields + ') ';
query += ' SELECT TOP 1 ' + values;
query += ' FROM ' + table;
query += ' WHERE NOT EXISTS ( SELECT 1 FROM ' + table + ' WHERE ';
array.forEach(function(element)
{
query += "([" + element.name + "]=" + element.value + ") AND ";
});
query = query.substr(0, query.length - 4);
query += ' );';
dbConnection
.execute(query)
.on('done', function (data) {
return callback({id: id, success: true});
})
.on('fail', function (data) {
console.log(data);
return callback({id: id, success: false});
});
but it doesn't solved the problem, i.e. sometimes I still found two or more records identical in the database.
I'm afraid it could be the same behavior: the client make multiple requests in a while and they are executed in parallel, so each one doesn't find the record, and all will be add it.
Hance, what is the right approach to avoid this without change the database structure?
Is there a way to force node-adodb to execute only one query at time?

How to wait for the first function to finish executing before the second is executed?

I'm trying to figure out a way to wait until the first function (relatedVids(...)) to finish executing and then in turn execute the second function (relatedVidsDetails()). What the code does is simply loop through a single $.get(...) request from the youtube api and loop through each of its item retrieved.
The problem with this as tested in the debugger, is that it will go in the first function (up to the $.get() without actually getting anything yet) then skip into the second function (once again, up to the $.get()). Then, it will proceed to execute the first function till it's finished retrieving all items, then when it gets into the second function, it will do the same thing but for some mysterious reason, the videoIdChainStr which holds all the video ids in a string from the first function is never retrieved or being executed since I suspected it executed the second function's $.get(...) already and never did it again a "second time" when it had the values.
So, my next step is trying to use $.Deferred() which is said to help resolve the first function first before even stepping into executing the second function so it will guaranteed values from the first function to be used in the second without skipping anything. But I'm not sure if I'm doing this right as it still does the same thing with or without using $.Deferred().
First function (relatedVids(...)):
var relatedVidsDefer = function relatedVids(videoId)
{
var r = $.Deferred();
$.get( // get related videos related to videoId
"https://www.googleapis.com/youtube/v3/search",
{
part: 'snippet',
maxResults: vidResults,
relatedToVideoId: videoId,
order: 'relevance',
type: 'video',
key: 'XXXXXXXXXX'
},
function(data)
{
debugger;
$.each(data.items,
function(i, item)
{
try
{
console.log(item);
var vidTitle = item.snippet.title; // video title
var vidThumbUrl = item.snippet.thumbnails.default.url; // video thumbnail url
var channelTitle = item.snippet.channelTitle; // channel of uploaded video
var extractVideoId = null; // var to extract video id string from vidThumbUrl
// check if vidThumbUrl is not null, empty string, or undefined
if(vidThumbUrl)
{
var split = vidThumbUrl.split("/"); // split string when '/' seen
extractVideoId = split[4]; // retrieve the fourth index on the fourth '/'
}
else console.error("vidThumbUrl is either undefined or null or empty string.");
// if video title is longer than 25 characters, insert the three-dotted ellipse
if(vidTitle.length > 25)
{
var strNewVidTitle = vidTitle.substr(0, 25) + "...";
vidTitle = strNewVidTitle;
}
// check whether channelTitle is March of Dimes
if(channelTitle === "March of Dimes")
{
extractedVideoIdArr.push(extractVideoId); // add the extracted video id to the array
// check if extractedVideoIdArr is not empty
if(extractedVideoIdArr !== 'undefined' && extractedVideoIdArr.length > 0)
{
console.log("before join(): ", extractedVideoIdArr);
videoIdChainStr = extractedVideoIdArr.join(", "); // change from an array to a chain string of videoIds for the relatedVidsDetails()
console.log("after join(): ", videoIdChainStr);
}
var vidThumbnail = '<div class="video-thumbnail"><a class="thumb-link" href="single-video.html"><div class="video-overlay"><img src="imgs/video-play-button.png"/></div><img src="' + vidThumbUrl + '" alt="No Image Available." style="width:204px;height:128px"/></a><p><a class="thumb-link" href="single-video.html">' + vidTitle + '</a><br/></div>'; //+ convert_time(vidDuration) + ' / Views: ' + viewCount + '</p></div>';
// print results
$('.thumb-related').append(vidThumbnail);
$(item).show(); // show current video thumbnail item
}
else $(item).hide(); // hide current video thumbnail item
}
catch(err)
{
console.error(err.message); // log error but continue operation
}
}
);
}
);
return r;
};
Second function (relatedVidsDetails(...)):
var relatedVidsDetailsDefer = function relatedVidsDetails()
{
// change extractvideoid into a string by tostring() or join() for param to recognize
console.log("initial: ", extractedVideoIdArr);
$.get(
"https://www.googleapis.com/youtube/v3/videos",
{
part: 'snippet, contentDetails, statistics',
id: videoIdChainStr, // chain string of video ids to be called upon in a single request
key: 'XXXXXXXXXX',
},
function(data)
{
debugger;
$.each(data.items,
function(i, item)
{
try
{
console.log("relatedVidsDetails()", item);
console.log("extractedvideoidarr: ", extractedVideoIdArr[i]);
var _vidDuration = item.contentDetails.duration;
var _viewCount = item.statistics.viewCount;
console.log("id: " + extractedVideoIdArr[i] + " duration: " + _vidDuration);
console.log("id: " + extractedVideoIdArr[i] + " viewCount: " + _viewCount);
}
catch(err)
{
console.error(err.message); // log error but continue operation
}
}
);
}
);
};
Code being skipped when the second function has been tapped into the second time:
$.each(data.items,
function(i, item)
{
try
{
console.log("relatedVidsDetails()", item);
console.log("extractedvideoidarr: ", extractedVideoIdArr[i]);
var _vidDuration = item.contentDetails.duration;
var _viewCount = item.statistics.viewCount;
console.log("id: " + extractedVideoIdArr[i] + " duration: " + _vidDuration);
console.log("id: " + extractedVideoIdArr[i] + " viewCount: " + _viewCount);
}
catch(err)
{
console.error(err.message); // log error but continue operation
}
}
);
The code being skipped due to the second function being stepped into when videoIdChainStr was empty and then skipped when first function completed and had the values ready for the second to use. I couldn't get this videoIdChainStr when it has values to then execute.
Well the answer to the question: "How to wait for the first function to finish executing before the second is executed" would be:
USE CALLBACK OR PROMISES.
Example:
function firstMethod() {
// half of first method actions ...
secondMethod(function() {
// other half of first method actions
});
}
function secondMethod(callBack) {
// Second method actions
setTimeout(function() { // The timeout is just a way to simulate the time that a response make us wait
callBack();
}, 5000);
}

listbox does not pass string to google script

When I select a user from listbox, the onChange() event triggers a function. It should pass a string to the function. Then the code finds the user's password and returns it for comparison. The following is the code which works fine if I hard code the user value, but not when I select it from the listbox.
function addClients(clients){
$('#customer').empty();
$('#customer').append('<option> ---- Choose a user ----</option>');
for (var i in clients) {
$('#customer').append('<option>'+clients[i]+'</option>');
$('#customer').trigger("chosen:updated");
}
}
getval function:
function getval(sel){
var usrpass = google.script.run.getuserpass(sel.value);
alert(usrpass);
}
the function in code.gs is as follows
function getuserpass(userval){
var usrpass = "";
var doc = SpreadsheetApp.openById("spreadsheet id");
var sheet = doc.getActiveSheet();
var data = sheet.getRange(3, 3, sheet.getLastRow(),5).getValues();;
for(n=0;n<data.length;++n){
// iterate row by row and examine data in column A
if(data[n][0].toString().match(userval)==userval){ usrpass = data[n][4]};
}
return usrpass;
}
Why does the return value come back as undefined rather than the password.
If I hardcode username in the function and run the function, then the return value is the value in the fifth column.
Try structuring the code like this:
<script>
function onSuccess(returnVal) {
alert('Success! ' + returnVal);
};
function getval(sel){
var selectValue = sel.value;
console.log('selectValue: ' + selectValue);
google.script.run
.withSuccessHandler(onSuccess)
.getuserpass(sel.value);
};
</script>
You can iterate through the object to see what is really in it, as a debugging test.
for (var propertyVal in sel) {
console.log('this property: ' + propertyVal);
console.log('this value: ' + sel[propertyVal]);
};
And see what is really in the object.

Categories