Background
I've been writing a library in Google App Script for my company that sends out an email based on parameters a user assigns. This function is similar to Google's MailApp.sendEmail() function. Building off of that function, I've been working on a way that allows a user to take a sheet from a spreadsheet and send that as a PDF in the email as well. This is determined through an object {sheet: 'sheet to be PDF', name: 'PDF name', type: 'portrait'}, with the sheet key determining which sheet will be converted and sent as a PDF, name key determining the name of the PDF, and type key being whether the PDF is sent out in either portrait or landscape mode. An example of what this function call would look like is: sendEmail('person#gmail.com', 'subject', 'body', 'otherperson#gmail.com', {sheet: 'sheet1', name: 'PDF_name', type: 'landscape'}. So in this case, an email will be sent to the email address person#gmail.com with otherperson#gmail.com being cc'd to the email. The email that is sent, the subject line will read as subject, with the body of the email only saying body. The object at the end does something further. If your code is attached to a spreadsheet, it will locate the sheet with the name sheet1, convert that into a PDF, setting the name of the newly created PDF as PDF_name and setting that PDF in landscape mode.
I have it set up so that if someone wants to send a sheet as a PDF, but they do not specify what they want the name of the PDF to be set to or type of orientation they want the PDF to be in, then the name of the PDF will just be the sheet name and the type will just be portrait mode. The issue that arises is in a switch-case statement, specifically this one:
Assumed Problem Code
if(arguments.length >= 4){
switch(true){
case validateEmail(arguments[3]):
options.cc = arguments[3]
case (isObject(arguments[3]) && arguments.length === 4)|| isObject(arguments[4]):
options.pdf = {
sheet: arguments[3].sheet,
name: arguments[3].name,
type: arguments[3].type
}
tempName(options)
}
}
My understanding of how switch-case statements work is when the switch argument is set to true, it will only execute the specific case if the argument/value/whatever returns true. An example case where it keeps failing is when the function is called, the arguments are sendEmail('person#gmail.com', 'subject', 'body', 'otherperson#gmail.com').
In my example, since there are 4 arguments, it should run through the switch-case, and should return true for the first case, as it is an email. It would then proceed to the next case because there is no break after the first case. However, it should return false because the email will not be an object, but a string. The issue is that even though it does not pass that case, it still creates the object and adds it to the options object that was created earlier in the script. I'm not sure why this is happening, as not passing that case shouldn't create that object, and shouldn't then proceed to the tempName() function call. Any assistance or advice is appreciated. I will also link all of the remaining relevant code below:
This is not relevant to the issue, but I should explain it as well. The tempName() function call is just for checking when a user IS adding the object, it checks if those values are null. It then sets them to default parameters so the PDF will have the same name as the sheet being converted into a PDF and type of PDF will be portrait mode. It then proceeds to another function call that converts it into a PDF properly.
Relevant Code
function sendEmail(){
try{
const options = {
to: arguments[0],
subject: arguments[1],
body: arguments[2],
}
if(arguments.length >= 4){
switch(true){
case validateEmail(arguments[3]):
options.cc = arguments[3]
case (isObject(arguments[3]) && arguments.length === 4)|| isObject(arguments[4]):
options.pdf = {
sheet: arguments[3].sheet,
name: arguments[3].name,
type: arguments[3].type
}
tempName(options)
}
}
function tempName(options){
Logger.log(options)
switch(true){
case options.pdf.name == null:
options.pdf.name = options.pdf.sheet
case options.pdf.type == null:
options.pdf.type = 'portrait'
}
return options.attachments = [pdf(options.pdf.sheet, options.pdf.type).setName(options.pdf.name)]
}
MailApp.sendEmail(options)
} catch(e){
Logger.log(e)
}
}
Other Potentially Relevant Code
The PDF converter function
function pdf(sheetName, type){
let ss_id = spreadsheet.getId()
let sheet_id = spreadsheet.getSheetByName(sheetName).getSheetId()
let url
//Check for type submitted
switch(type){
//Spreadsheet converted to PDF in portrait mode
case 'portrait':
url = "https://docs.google.com/spreadsheets/d/" + ss_id + "/export?format=pdf&gid=" + sheet_id
break
//Spreadsheet converted to PDF in landscape mode
case 'landscape':
url = "https://docs.google.com/spreadsheets/d/" + ss_id + "/export?format=pdf&portrait=false&gid=" + sheet_id
break
//If neither option works, then user did no enter 'portrait' or 'landscape' as their type option
default:
throw `${type} is not a valid PDF format`
}
let response = UrlFetchApp.fetch(url, {
muteHttpExceptions: true,
headers: {
Authorization: 'Bearer ' + ScriptApp.getOAuthToken(),
},
}).getBlob()
return response
}
The email validator function. I cannot use APIs currently, so this is my current workaround to validate if an email exists.
function validateEmail(email){
let ss = SpreadsheetApp.openByUrl(SpreadsheetApp.create('Email Validation Spreadsheet', 1, 1).getUrl())
if(!new RegExp('[#]').test(email)){
return false
} else{
try{
ss.addViewer(email)
} catch(e){
setTrashed()
return false
}
setTrashed()
return true
}
function setTrashed(){
DriveApp.getFilesByName('Email Validation Spreadsheet').next().setTrashed(true)
}
}
Checker to make sure something is an object
//Checker to make sure an object is an object
function isObject(obj){
if(obj != null && obj.constructor.name === 'Object'){
return true
} else{
return false
}
}
Suggestion: Replace the Switch Statements (with Issues) with If Statements
You can achieve the concept you wanted for your switch statements with if statements since the current switch statement that you use is not applicable in this case.
Script:
Based on your post, you can replace the part of your script from:
if (arguments.length >= 4) {
switch (true) {
case validateEmail(arguments[3]):
options.cc = arguments[3]
case (isObject(arguments[3]) && arguments.length === 4) || isObject(arguments[4]):
options.pdf = {
sheet: arguments[3].sheet,
name: arguments[3].name,
type: arguments[3].type
}
tempName(options)
}
}
To:
if (arguments.length >= 4) {
if (validateEmail(arguments[3]))
options.cc = arguments[3];
if ((isObject(arguments[3]) && arguments.length === 4) || isObject(arguments[4])) {
options.pdf = {
sheet: arguments[3].sheet,
name: arguments[3].name,
type: arguments[3].type
};
tempName(options);
}
}
Additional Analysis:
Let me present some test cases with their corresponding output.
Switch Statement with Break Statements
function test1() {
var data = 7;
switch (true) {
case (data < 5):
console.log("Less than 5");
break;
case (data < 10):
console.log("Less than 10");
break;
case (data > 5):
console.log("Greater than 5");
break;
case (data > 10):
console.log("Greater than 10");
break;
}
}
From this, the expected output should like the one below which is lacking:
This is the basic concept of switch statements wherein only the case which first matched with the switch expression is executed and then terminate the whole switch statement because of the break statement.
Switch Statement without Break Statements
function test2() {
var data = 7;
switch (true) {
case (data < 5):
console.log("Less than 5");
case (data < 10):
console.log("Less than 10");
case (data > 5):
console.log("Greater than 5");
case (data > 10):
console.log("Greater than 10");
}
}
From this, the expected output should be the one below which included an incorrect output:
Proposed Solution: Multiple If Statements
function test3() {
var data = 7;
if (data < 5) {
console.log("Less than 5");
}
if (data > 5) {
console.log("More than 5");
}
if (data < 10) {
console.log("Less than 10");
}
if (data > 10) {
console.log("More than 10");
}
}
From this, the expected output should look like the one below which should be just right:
Reference:
JavaScript Switch Statement
Related
I've been looking through questions on SO all day but I feel like I'm not getting anywhere here so I turn to the community. Hope you clever folks can help me out.
I've got the following controller action in my project:
[HttpGet("export")]
public IActionResult ExportData(string exportType, DateTime? fromDate, DateTime toDate, string search, bool? locked)
{
// Load the data from the db.
var data = LoadData(fromDate, toDate, locked, search);
bytes[] contentBytes = new byte[] { };
switch (exportType)
{
case "csv":
contentBytes = DataHelpers.ExportDataToCSV(data);
break;
// Other cases removed for brevity.
}
var content = new MemoryStream(contentBytes);
return File(content, MediaTypeNames.Application.Octet, "Report.csv");
}
I'm honestly not sure if the export works because I keep getting an HTTP 404 on my ajax query to it:
function dataExport(type) {
const search = document.getElementById("GridSearch").value;
const fromDate = document.getElementById("startDate").value;
const toDate = document.getElementById("endDate").value;
const locked = document.getElementById("LockedStatus").checked;
let args = "?exportType=" + type + "&";
if (search !== null && search.length >= 1) {
args += "search=" + search + "&";
}
if (fromDate !== null && fromDate.length >= 1) {
args += "fromDate=" + fromDate + "&";
}
if (toDate !== null && toDate.length >= 1) {
args += "toDate=" + toDate + "&";
}
if (locked === true || locked === false) {
args += "locked=" + locked
}
if (args.endsWith("&")) {
args = args.substr(0, args.length - 1);
}
// Path is /[area]/[controller]/[action]
// Url output = /r/OtpLock/export?exportType=csv&search=foo
$.get("/r/OtpLock/export" + args, null, function (data) {
console.log(data);
});
}
I wouldn't have expected a 404 here since it's really just hitting the controller action so I think maybe my routing isn't working?
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapAreaControllerRoute(
name: "areas",
areaName: "areas",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
});
Have I made a mistake somewhere? Is it the routing?
Thanks in advance!
With a route template of [Route("r/[controller]/[action]")] specified on the controller, the route for the ExportData action becomes:
r/OtpLock/ExportData
Adding [HttpGet("export")] to the ExportData method appends an existing segment, export, which changes its route to:
r/OtpLock/ExportData/export
This isn't the URL you're using for your AJAX calls, so the server responds with a 404.
To make this behave as you expect, there are a few options. e.g.:
Use [ActionName("export")] instead of [HttpGet("export")]. This has the effect of providing export as the value for [action], rather than the default, which is the name of the method, ExportData. It also doesn't add anything extra to the route defined at the controller level.
Remove the [HttpGet("export")] attribute and rename the ExportData action at the code level, by renaming the method to Export instead of ExportData.
You might be able to remove both the [Route(...)] attribute from the controller and the [HttpGet(...)] attribute from the action. This would revert to using convention-based routing, which you've set up with MapAreaControllerRoute. This would also require either #1 or #2 above, but I'm not 100% on whether this will work for your setup.
I am creating a discord bot that will take data from a website, and display it in an embed.
I stored the data in an array called boxArr. Then I iterate over it, store the data temporarly, and assign it to the correct embed's fields' properties (namely title and value).
It works fine for some pages, but not for others. Whenever if fails, I get an
[UnhandledPromiseRejectionWarning: DiscordAPIError: Invalid Form Body embed.fields[0].value: Must be 1024 or fewer in length.] warning.
However the embed's values never exceeds 1024 characters in length, so I am baffled as to why I am getting this warning.
Here is my code:
//Create the embed
var exampleEmbed = new Discord.MessageEmbed()
.setColor('#2D2D2D')
.setTitle(str)
.setURL('/*page link here*/'+str)
.setThumbnail(th)
.addField('', def + `[Read More...](/*page link here*/${str})`);
//Variable to hold the decision if titles are even or odd
var toOdd = true;
for(i = 0; (boxArr[i] != undefined || boxArr[i] != null); i++) {
if(boxArr[i] === '' || boxArr[i].startsWith('"')) {
if(isOdd(i) === 1) { //If boxArr[i] is nothing and i is odd, titles' location is when i is even
toOdd = false;
continue;
} else { //If boxArr[i] is nothing and i is even, titles' location is when i is odd
toOdd = true;
continue;
}
}
if(toOdd) {
if(isOdd(i) === 1) {
title = boxArr[i]; //Assign title its value
i++; //Increment i
val = boxArr[i];
if(val === undefined || title === undefined) { //Check if one of the two is undefined. If yes then break out without tampering with the embed.
break;
}
Embed.addFields({name: title, value: val, inline: true});
if(boxArr[i+1] === undefined) { //This is unnecessary but I added it nevertheless
break;
}
continue;
}
} else if(!toOdd) { //Similar to if toOdd === true
if(isOdd(i) != 1) {
title = boxArr[i];
i++;
console.log('\n\nVALUE: '+ boxArr[i]);
val = boxArr[i];
if(val === undefined || title === undefined) {
break;
}
Embed.addFields({name: title, value: val, inline: true});
if(boxArr[i+1] === undefined) {
break;
}
continue;
}
}
}
message.channel.send(Embed); //send the embed
Any help is appreciated.
So I found the problem.
Apparently the problem is that the embed itself has over 1024 characters.
Removing [Read More...](/*page link here*/${str}) from .addField('', def + [Read More...](/*page link here*/${str})); solved the problem.
EDIT: Another fix is to limit str to 500 characters.
Hoping there is a SCORM/Javascript pro out there who can help this noob!
I have a SCORM file authored in Articulate Rise. Initially the cmi.core.lesson_status is set to "incomplete". At the end there is a quiz which allows two attempts. After the first failed attempt, the lesson_status updates to "failed". It stays that way unless you pass.
Instead, I want it to stay as "incomplete" after the first attempt if you fail. Then, after the second attempt, I want it to update to "passed" or "failed" based on your score.
Here is how the code was written orginally:
var reporting = 'passed-failed';
function completeOut(passed, reportParam) {
var reportType = reportParam || reporting;
if(passed) {
switch(reportType) {
case 'completed-incomplete':
case 'completed-failed':
LMSProxy.ResetStatus();
LMSProxy.SetReachedEnd();
break;
case 'passed-incomplete':
case 'passed-failed':
LMSProxy.SetPassed();
LMSProxy.SetReachedEnd();
break;
}
} else {
switch(reportType) {
case 'passed-failed':
case 'completed-failed':
if(!isPassed()) {
LMSProxy.SetFailed();
}
break;
}
}
}
After looking at it, I tried to fix it with this code. Note that data.retryAttempts is 0 on the first attempt and 1 on the second attempt.
function completeOut(passed, reportParam) {
var reportType = reportParam || reporting;
var attemptNumber = data.retryAttempts
if(passed) {
switch(reportType) {
case 'completed-incomplete':
case 'completed-failed':
LMSProxy.ResetStatus();
LMSProxy.SetReachedEnd();
break;
case 'passed-incomplete':
case 'passed-failed':
LMSProxy.SetPassed();
LMSProxy.SetReachedEnd();
break;
}
} else if(attemptNumber == 1) {
switch(reportType) {
case 'passed-failed':
case 'completed-failed':
if(!isPassed()) {
LMSProxy.SetFailed();
}
break;
}
}
}
The above code didn't work. Here's a link to the JS errors returned.
----edit----
Here is a link to the SCORM package: https://drive.google.com/file/d/1xOJyxzBUD1A43jMykPBHUe3Knzpc_Ec6/view?usp=sharing
I am currently using the strict === comparison in a javascript, The comparison produces expected result as required on my machine but when deployed to server it doesn't, it behaves differently.
Script
computed: {
type: function(){
switch(this.job.type)
{
case 1:
return 'Request Quote';
case 2:
return 'Negotiate';
case 3:
return 'Fixed';
default:
return 'matched none';
}
},
}
Template
<p class="text-primary"><strong>{{type}}</strong></p>
Output On Local Server for job type of 2
Output On Production Server for job type of 2
If i switch code to a loosed comparison If statement, it works well
If statement
computed: {
type: function(){
if(this.job.type == 1) return 'Request Quote';
else if(this.job.type == 2) return 'Negotiate';
else if(this.job.type == 3) return 'Fixed';
else return 'matched none';
},
}
Result in Production Server
As you may have noticed I am making use of the VUEJS framework, also the job object is database model fetched with axios. I am also making use of Laravel on the backend.
Could it be an issue with the mysql version?
Version runnning on Local Machine
Version running in production
That means that your code/config/etc. in production is creating a string, or a number object (rather than primitive), or some other kind of object that when coerced to number coerces to one of your expected values.
Example with a string:
function strict(type) {
switch(type)
{
case 1:
return 'Request Quote';
case 2:
return 'Negotiate';
case 3:
return 'Fixed';
default:
return 'matched none';
}
}
function loose(type) {
if(type == 1) return 'Request Quote';
else if(type == 2) return 'Negotiate';
else if(type == 3) return 'Fixed';
else return 'matched none';
}
var n;
console.log("Number:");
n = 2;
console.log("strict", strict(n));
console.log("loose", loose(n));
console.log("String:");
n = "2";
console.log("strict", strict(n));
console.log("loose", loose(n));
Example with a number object:
function strict(type) {
switch(type)
{
case 1:
return 'Request Quote';
case 2:
return 'Negotiate';
case 3:
return 'Fixed';
default:
return 'matched none';
}
}
function loose(type) {
if(type == 1) return 'Request Quote';
else if(type == 2) return 'Negotiate';
else if(type == 3) return 'Fixed';
else return 'matched none';
}
var n;
console.log("Primitive:");
n = 2;
console.log("strict", strict(n));
console.log("loose", loose(n));
console.log("Number object:");
n = new Number(2);
console.log("strict", strict(n));
console.log("loose", loose(n));
You'll have to find out what is different in the environment/code/config such that you're ending up with the wrong kind of value for this.job.type.
Check your variable type, I think you're getting a string in production too.
I suggest you using Typescript or Flowtype to avoid those check.
If my javascript ajaxes away to my server and returns an ID of 49 in the plain text format of [49] is there a way in which i an do something like this... (i have tested and doesnt work)
switch(data)
{
case '[*]':
(..etc.)
break;
}
Where the wildcard is the * and i want to make sure it is enclosed within two square parenthesis?
Because i need to check that there wasnt another word returned like error and i am reserving the default for unexpected errors, any ideas? :) Thanks!
You can do a switch on true explicitely, which will use evaluation on each case statement.
switch (true) {
case ((/^\[\d+\]$/).test(data)):
//matches data;
break;
case (data == "something else"):
//...
break;
default:
//...
}
However, if you have less than say 4-5 cases, it would be better to use if/else if/else if/else blocks.
if ((/^\[\d+\]$/).test(data)) {
//matches data;
} else if (data == "something else") {
//...
} else {
//...
}
I usually do some error trapping in my response methods for service/rest calls so that I almost always return a proper json with an error property if there is an error.
try {
if (response.responseText.indexOf("<html") >= 0) {
throw response.responseText;
}
var data = JSON.parse(response.responseText);
if (data.error)
throw data.error;
//handle response data object.
if ((/^\[\d+\]$/).test(data)) {
//matches data;
} else if (data == "something else") {
//...
} else {
//...
}
} catch(err) {
if (err && err.message) {
//use err.message
} else if (err && err.toString().indexOf("<html") >= 0) {
//handle error text
}
}
You could create a list of patterns and associated callbacks and do a simple loop and check for matches. For example:
var patterns = [];
function myCallback(){ document.write('myCallback!'); }
function myOtherCallback(){ document.write('myOtherCallback!'); }
function myLastCallback(){ document.write('You will never see me!'); }
patterns.push({'pattern':new RegExp(/\[.+\]/),'callback': myCallback});
patterns.push({'pattern':new RegExp(/.+/),'callback':myOtherCallback});
patterns.push({'pattern':new RegExp(/A-Z{3}/),'callback':myLastCallback});
var f = "[49]";
for(var i=0;i<patterns.length;i++){
if(patterns[i].pattern.test(f)){
patterns[i].callback();
}
}
Which outputs the following:
myCallback!myOtherCallback!
You could try to use if else and regex for matching wildcard patterns.
Assuming data = "[49]"; or any digits inside brackets.
if(/\[\d+\]/.test(data)){
//do something
}else{
//default
}
Short answer: No, switch/case can't handle wildcard.
You should probably do some preprocessing/sanity checking before entering the switch, or simply discard it completely since it's more appropriate for specific case scenarios rather than processing streamlined data. Regexp will serve you better here.