Google Apps Script - Email stock notification? - javascript

Hi there!
I am a beginner both in JavaScript and in Google Sheets, but I am trying to find a way for Google Apps Script to basically scan the data I have brought in there from a Swedish Online Bank where they have some information about how the stocks go up and down.
Furthermore, I want to be notified by email when one of these on my list goes down by for example 5 % in a day.
I tried something like this:
let arrayRow = ["+" + 5.91 + "%", "+" + 5.22 + "%", "-" + 5.5 + "%"];
console.log(arrayRow);
function stockPricePlus() {
if (arrayRow >= "+" + 5 + "%") {
console.log("Yay! One of your stocks are going up by 5 % or more!");
}
}
function stockPriceMinus() {
if (arrayRow <= "-" + 5 + "%") {
console.log("Oh noes! One of your stocks are going down by 5 % or more!");
}
}
stockPricePlus();
stockPriceMinus();
And this works in my JavaScript file, but I am not quite sure how to make it pull the data continuously from the Google Sheets and run through them like a loop?
I found something on the internet that seemed to kind of do the job, but I also see that there are some missing parts in the code.
function sendEmails () {
var sheet = SpreadsheetApp.getActiveSheet();
var Price = sheet.getRange("B34:B").getValues();
var data = Price.getValues();
var results = [];
for (var i = 0; i < data.length; ++i) {
var row = data[i];
Logger.log(Price);
if (Price >= "+" + 5 + "%") {
MailApp.sendEmail("johnsmith#gmail.com", "Stock Price Alert from Stock Price Google Script", "One of your stocks are going up by 5 % or more!");
}
if (Price <= "-" + 5 + "%") {
MailApp.sendEmail("johnsmith#gmail.com", "Stock Price Alert from Stock Price Google Script", "One of your stocks are going down by 5 % or more!");
}

A ClientSide Timer for Collecting Periodic Stock Prices using GoogleFinance cell formulas
This code is a portion of code that I have used to check stocks. It has a timer function which runs clientside on your browser and you can adjust the sampling rate as you desire. I'd recommend no less that once every 5 minutes. That gives a good long time to get everything done. I also added a checkStats function which calculates percent change using the formula (max-min/max) * 100 and it compares this value with a value that you can set for each stock on the StockPrices page. It is also set up to send emails if the percent change is greater than a threshold and you can set. You can have as many stocks as you wish but you may need to adjust the sample rate if you try to get too many. You will have to add the email recipient address.
I have several other functions which chart the various stocks in different ways that I didn't include in this. I tried to keep this simple so I wouldn't be surprised if I have inadvently left some things out. Please note this script does not start automatically each day. In fact I hardly ever use it but I thought it would be an interesting thing to do and since then I've found the timer portion to be quite handy.
It's been my experience that GoogleFinance tags do not refresh regularly throughout the day. I've seen them not change at all for as long as 12 minutes while watching the stock prices change on another more elaborate system that runs on a personal computer.
datatimer.html:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<style>
#my_block{border:2px solid black;background-color:rgba(0,150,255,0.2);padding:10px 10px 10px 10px;}
#conv_block{border: 1px solid black;padding:10px 10px 10px 10px;}
.bttn_block{padding:5px 5px 0px 0px;}
.sndr_block {border:1px solid rgba(0,150,0,0.5);background-color:rgba(150,150,0,0.2);margin-bottom:2px;}
</style>
</head>
<body>
<form>
<div id="my_block" class="block form-group">
<div class="sndr_block">
<div id="myClock" style="font-size:20px;font-weight:bold;"></div>
<br />Timer Duration(minutes):
<br /><input id="txt1" type="text" size="4" class="action"/>
<select id="sel1" onChange="loadTxt('sel1','txt1');">
</select>
<div id="cntdiv"></div>
<br /><strong>Timer Controls</strong>
<div class="bttn_block"><input type="button" value="Start" name="startShow" id="startShow" onClick="startmytimer();changeData();" class="red" /></div>
<div class="bttn_block"><input type="button" value="Stop" name="stopTimer" id="stopTimer" class="red" /></div>
<div class="bttn_block"><input type="button" value="Single Ping" name="changedata" id="chgData" class="red" onClick="changeData();" /></div>
</div>
<div id="btn-bar">
<br /><input type="button" value="Exit" onClick="google.script.host.close();" class="green" />
</div>
</div>
</form>
<script>
var idx=1;
var myInterval='';
var cnt=0;
$(function() {
var select = document.getElementById('sel1');
select.options.length = 0;
for(var i=1;i<61;i++)
{
select.options[i-1] = new Option(i,i * 60000);
}
select.selectedIndex=4;
$('#startTimer').click(startmytimer);
$('#stopTimer').click(stopTimer);
$('#txt1').val(String(select.options[select.selectedIndex].value));
startTime();
});
function startTime(){
var today = new Date();
var h = today.getHours();
var m = today.getMinutes();
var s = today.getSeconds();
m = checkTime(m);
s = checkTime(s);
document.getElementById('myClock').innerHTML =
h + ":" + m + ":" + s;
var t = setTimeout(startTime, 500);
}
function checkTime(i){
if (i < 10) {i = "0" + i}; // add zero in front of numbers < 10
return i;
}
function startmytimer(){
document.getElementById('cntdiv').innerHTML='<strong>Timer Started:</strong> ' + document.getElementById('myClock').innerHTML;
myInterval=setInterval(changeData, Number($('#txt1').val()));
}
function stopTimer(){
document.getElementById('cntdiv').innerHTML='Timer Stopped';
clearInterval(myInterval);
}
function loadTxt(from,to){
document.getElementById(to).value = document.getElementById(from).value;
}
function changeData(){
$('#txt1').css('background','#ffffcc');
google.script.run
.withSuccessHandler(updateDisplay)
.changeData();
}
function updateDisplay(t){
$('#txt1').css('background','#ffffff');
document.getElementById('cntdiv').innerHTML='<strong>Timer Running:</strong> Count= ' + ++cnt + ' <strong>Time:</strong> ' + t;
}
console.log('My Code');
</script>
</body>
</html>
Code.gs:
function onOpen(){
SpreadsheetApp.getUi().createMenu('MyTools')
.addItem('Show Timer SideBar', 'showTimerSideBar')
.addToUi();
}
//This is the function driven by the clientside timer trigger It also creates new data sheets for each day.
function changeData(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('StockPrices');
var rg=sh.getRange(3,1,1,sh.getLastColumn());
var vA=rg.getValues();
var n=new Date();
var tmr=Utilities.formatDate(n, Session.getScriptTimeZone(), "HH:mm:ss");
var ts=Utilities.formatDate(n, Session.getScriptTimeZone(), "E-MMddyy-HHmmss");
var sheetTitle=Utilities.formatDate(n, Session.getScriptTimeZone(), "E-MMddyy");
vA[0][0]=ts;
if(isSheet(sheetTitle)){
ss.getSheetByName(sheetTitle).appendRow(vA[0]);
}else{
var sht=ss.insertSheet(sheetTitle);
var hA=sh.getRange(1,1,1,sh.getLastColumn()).getValues();
hA[0][0]="TimeStamp";
sht.appendRow(hA[0]);
sht.appendRow(vA[0]);
}
checkStats(sheetTitle);
return tmr;
}
function showTimerSideBar()
{
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('StockPrices');
sh.getRange(5,2,1,sh.getLastColumn()-1).clearContent();//clears the sent row
var ui=HtmlService.createHtmlOutputFromFile('datatimer').setTitle('Javascript Trigger Generator');
SpreadsheetApp.getUi().showSidebar(ui);
}
function isSheet(sheetname){
var r=false;
var ss=SpreadsheetApp.getActive();
var allSheets=ss.getSheets();
for(var i=0;i<allSheets.length;i++){
if(allSheets[i].getName()==sheetname){
r=true;
break;
}
}
return r;
}
//This function checks stats and compares them to limits to determine if warning email messages should be sent
function checkStats(page) {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName(page);
var rg=sh.getRange(1,2,sh.getLastRow(),sh.getLastColumn()-1);
var vA=rg.getValues();
var minA=vA[1].slice(0);
var maxA=vA[1].slice(0);
var pchA=[];
for(var i=2;i<vA.length;i++) {
for(var j=0;j<vA[i].length;j++) {
if(vA[i][j]>maxA[j]) {
maxA[j]=vA[i][j];
}
if(vA[i][j]<minA[j]) {
minA[j]=vA[i][j];
}
}
}
for(var i=0;i<minA.length;i++) {
pchA.push(Number(((maxA[i]-minA[i])/maxA[i]) * 100).toFixed(2));
}
var spsh=ss.getSheetByName('StockPrices');
var limitA=spsh.getRange(4,2,1,spsh.getLastColumn()-1).getValues();
var nameA=spsh.getRange(1,2,1,spsh.getLastColumn()-1).getValues();
var sentA=spsh.getRange(5,2,1,spsh.getLastColumn()-1).getValues();
var msgA=[];
for(var i=0;i<pchA.length;i++) {
if(pchA[i]>limitA[i] && sentA[i]!="SENT") {
msgA.push({name:nameA[i],change:pchA[i],limit:limitA[i],index:i});
}
}
if(msgA.length>0){
var html="<h1>Stocks Exceeding Change Limit</h1>";
var text='Stocks Exceeding Change Limit\n';
for(var i=0;i<msgA.length;i++) {
html+=Utilities.formatString('<br />Stock Name: <strong>%s</strong><br />Limit: <strong>%s</strong><br />Change: <strong>%s</strong><hr width="100%"/><br />', msgA[i].name,msgA[i].limit,msgA[i].change);
text+=Utilities.formatString('\nStock Name: %s\nLimit: %s\nChange: %s\n\n', msgA[i].name,msgA[i].limit,msgA[i].change);
sentA[msgA[i].index]="SENT";
}
//GmailApp.sendEmail(recipient, 'Stocks Exceeding Change Limit', text, {htmlBody:html})
spsh.getRange(5,2,1,spsh.getLastColumn()-1).setValues(sentA);
}
}
This is what the Stock Prices page looks like:
This is what a daily data page looks like:
And this is what the timer sidebar looks like:
Apps Script Documentation

Related

How to Append Increments in a Function Individually to a List?

My issue is I am not sure how to type the code to increment trainADistance by trainAMPM and decrease trainBDistance by trainBMPM in the do loop while having each text appended to the list. Each minute is supposed to be appended individually with text += "Minute " + i + " Train A Position: "+ parseFloat(trainADistance).toFixed(2) + " miles" + "Train B Position: " + parseFloat(trainBDistance).toFixed(2);. An example is listed below. Any help would be appreciated.
What the function is supposed to do?
The function I have been attempting to create is supposed to allow the user to input three variables trainAMPH, trainBMPH and distanceApart. When the inputs are submitted, the function function calculateTrains() is supposed to calculate trainAMPH, trainBMPH into miles per minute trainAMPM and trainBMPM. There is a do-while loop in place to increment the minutesi , increment trainADistance by trainAMPM and decrease trainBDistance by trainBMPM. trainBDistance is supposed to contain the same value as distanceApart. The loop continues while(trainADistance < trainBDistance) and is supposed to append text into a list until the loop is broken. An example is listed below.
This is an example of what is supposed to be appended to to the list with inputs 70 for train A, 60 for train B and 260 for distance
• Minute 1 Train A Position: 1.17 miles Train B Position: 259.00 miles
• Minute 2 Train A Position: 2.33 miles Train B Position: 258.00 miles
• Minute 3 Train A Position: 3.50 miles Train B Position: 257.00 miles
• Minute 4 Train A Position: 4.67 miles Train B Position: 256.00 miles
• Minute 5 Train A Position: 5.83 miles Train B Position: 255.00 miles
My Code
<script>
function calculateTrains(){
let trainAMPH= document.getElementById("trainAMPH").value;
let trainBMPH= document.getElementById("trainBMPH").value;
let distanceApart = trainBDistance = document.getElementById("distanceApart").value;
let list = document.getElementById("list");
//calculates MPH into MPM(miles per minute)
trainAMPM = (trainAMPH / 60);
trainBMPM = (trainBMPH / 60);
let trainADistance = 0;
let text = "";
let i = 0;
let li = "";
// do that increments the minutes and Train A/Train B's current position
d do{
text += "Minute " + i + " Train A Position: "+ parseFloat(trainADistance).toFixed(2) + " miles" + "Train B Position: " + parseFloat(trainBDistance).toFixed(2);
i++;
trainADistance += parseFloat(trainAMPM);
trainBDistance -= parseFloat(trainBMPM);
li = document.createElement("li");
}
// while loop tht continues while Train A distance is less than Train B distance
while(trainADistance < trainBDistance)
//adds "text" variable listed in do while loop to list
li.innerHTML = text;
list.appendChild(li);
// documents the amount of minutes to the "results" div
document.getElementById("results").innerHTML = "The two trains met after " + i + "minutes.";
}
</script>
<body>
<form id="myForm">
<label>Train A's Speed</label>
<input id="trainAMPH" type="number" value="" placeholder="MPH">
<label>Train B's Speed</label>
<input id="trainBMPH" type="number" value="" placeholder="MPH" >
<label>Distance between Eastford and Westford</label>
<input id="distanceApart" type="number" value=""placeholder="Miles">
<input type ="button" value="Submit Values" onclick="calculateTrains()">
<ul id = "list"></ul>
<div id="results"></div>
</form>
</body>
There were some issue with the code, I have made the necessary changes. Please go through the in-line comments that I have added in order to understand what were the mistakes and how they can be fixed.
The code is working now, please use the below snippet to run and see it's output. Do make sure to see my comments. Goodluck!
function calculateTrains() {
let trainAMPH = +document.getElementById("trainAMPH").value;
let trainBMPH = +document.getElementById("trainBMPH").value;
let distanceApart, trainBDistance;
//The input you retrieve using .value will be a string, convert it to Number using + to use toFixed
distanceApart = trainBDistance = +document.getElementById("distanceApart")
.value;
let list = document.getElementById("list");
//calculates MPH into MPM(miles per minute)
trainAMPM = trainAMPH / 60;
trainBMPM = trainBMPH / 60;
let trainADistance = 0;
let i = 1;
let text = "";
// do that increments the minutes and Train A/Train B's current position
do {
//don't use text+=, you don't want to append, you want each <li> to be on a separate line
text =
"Minute " +
i +
" Train A Position: " +
trainADistance.toFixed(2) +
" miles" +
"Train B Position: " +
trainBDistance.toFixed(2);
//create list item using template literals
let li = `<li>${text}</li>`;
//use insertAdjaventHTML( ) function to append the above created literal to list <ul>
list.insertAdjacentHTML("beforeend", li);
//increment i
i++;
//do the train A and train B distance calculations
//without these calculations, your while exit condition from below will never be met,
//making it an infinite while loop
trainADistance += trainAMPM;
trainBDistance -= trainBMPM;
} while (
// while loop tht continues while Train A distance is less than Train B distance
trainADistance < trainBDistance
);
//don't use innerHTML to append text, innerHTML is to append HTML string like '<div> hello <div>'
//instead use textContent or innerText properties
document.getElementById("results").textContent =
"The two trains met after " + i + "minutes.";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<body>
<form id="myForm">
<label>Train A's Speed</label>
<input id="trainAMPH" type="number" value="" placeholder="MPH" />
<label>Train B's Speed</label>
<input id="trainBMPH" type="number" value="" placeholder="MPH" />
<label>Distance between Eastford and Westford</label>
<input id="distanceApart" type="number" value="" placeholder="Miles" />
<input
type="button"
value="Submit Values"
onclick="calculateTrains()"
/>
<ul id="list"></ul>
<div id="results"></div>
</form>
</body>
<script src="script.js"></script>
</body>
</html>
the reason why trainBDistance.toFixed(2) isn't recognized as a function is because when call document.getElementById("distanceApart").value the data type is string u need to parseInt or parseFloat that value first. You condition is while(trainADistance < trainBDistance) so you need to increment trainADistance as you said.
This might help.
function calculateTrains(){
let trainAMPH= document.getElementById("trainAMPH").value;
let trainBMPH= document.getElementById("trainBMPH").value;
let distanceApart = trainBDistance = parseInt( document.getElementById("distanceApart").value );
let list = document.getElementById("list");
list.innerHTML = ''; //empty the list
//calculates MPH into MPM(miles per minute)
trainAMPM = (trainAMPH / 60);
trainBMPM = (trainBMPH / 60);
let trainADistance = 0;
let i = 0;
// do that increments the minutes and Train A/Train B's current position
do{
let text = "Minute " + i + " Train A Position: "+ trainADistance.toFixed(2) + " miles" + " Train B Position: " + trainBDistance.toFixed(2);
//adds "text" variable listed in do while loop to list
let li = document.createElement("li");
li.innerHTML = text;
list.appendChild(li);
i++;
trainADistance += trainAMPM; //increment trainADistance by trainAMPM
trainBDistance -= trainBMPM; //decrease trainBDistance by trainBMPM
}while(trainADistance < trainBDistance)
// while loop tht continues while Train A distance is less than Train B distance
// documents the amount of minutes to the "results" div
document.getElementById("results").innerHTML = "The two trains met after " + i + "minutes.";
}
You may wanna change you script to as follows:
<script>
function calculateTrains(){
let trainAMPH= document.getElementById("trainAMPH").value;
let trainBMPH= document.getElementById("trainBMPH").value;
let distanceApart = trainBDistance = document.getElementById("distanceApart").value;
let list = document.getElementById("list");
trainAMPM = (trainAMPH / 60);
trainBMPM = (trainBMPH / 60);
let trainADistance = 0;
let text = "";
let i = 0;
do{
i++;
trainADistance+=trainAMPM
trainBDistance-=trainBMPM
let li = document.createElement('li')
li.textContent="Minute " + i + " Train A Position: "+ parseFloat(trainADistance).toFixed(2) + " miles" + "Train B Position: " + parseFloat(trainBDistance).toFixed(2)
document.getElementById('list').appendChild(li)
}while(trainADistance<=trainBDistance)
}
</script>

Getting undefined at the start of an array in my GAS invoice generator using Google Docs

My script gets furniture prices & details from a Google spreadsheet and brings it into a Google docs add-on, this shows a sidebar where you select the range of furniture and spits out prices into the sidebar with an input field for the quantity once you hit generate invoice on the sidebar it then populates the Google doc table with the unit code, name, price, and quantity.
It's all working except for the first iteration of unitCode unitName unitPrice etc has undefined before it says the unitCode, unitPrice, unitName etc. Sorry about the mess of code it's very much a work in progress and hasn't been tidied or sample code really removed.
Also I feel like there's probably a much easier way to achieve what im trying to do, probably with just using G sheets itself so definitely open to suggestions.
Thanks in advance.
Photo of how undefined shows
Heres the HTML that sends to the GS file
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<!-- The CSS package above applies Google styling to buttons and other elements. -->
<style>
.units {
margin-top:20px;
width:100%;
}
</style>
</head>
<body>
<div class="sidebar branding-below">
<label>Client Name</label>
<input type="text" name="client_name" id="client_name"><br>
<label>Client Phone</label>
<input type="text" name="client_phone" id="client_phone"><br>
<label>Client Email</label>
<input type="text" name="client_email" id="client_email"><br>
<!-- Multiple Radios -->
<div class="form-group">
<label class="col-md-4 control-label" for="furntype">Furniture Type</label>
<div class="col-md-4">
<div class="radio">
<label for="furntype-0">
<input type="radio" name="furntype" id="furntype-0" value="0" checked="checked">
6S
</label>
</div>
<div class="radio">
<label for="furntype-1">
<input type="radio" name="furntype" id="furntype-1" value="1">
6S Compact
</label>
</div>
</div>
</div>
<span id="theFurnType"></span>
<form>
</form>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
/**
* On document load, assign click handlers to each button and try to load the
* user's origin and destination language preferences if previously set.
*/
$(function() {
$('input:radio').click(function() {
var sheetDataz = google.script.run
.withFailureHandler(function(err){
console.error("error occured", err);
})
.withSuccessHandler(function(res){
var tab = '<div class="units"><div >';
var price = res[2];
var theUnitCode = res[0];
$.each(res[1], function(index, value){
var aPrice = price[index];
var thePrice = parseFloat(aPrice).toFixed(2);
var unitCode = theUnitCode[index];
tab += '<span>'+value+'</span><input type="text" class="unit" unitcode="'+unitCode+'" unitname="'+value+'" id="'+index+'" price="'+thePrice+'" value="0" style="float:right; width:40px;"><hr>';
});
tab += '<br><button type="button" id="doInvoice">Create Invoice</button></div>';
$('#theFurnType').html(tab);
$('#doInvoice').click(replaceIt);
}).getSheetData($(this).val());
});
function replaceIt() {
var client_name = $('#client_name').val();
var client_phone = $('#client_phone').val();
var client_email = $('#client_email').val();
var acr = client_name.match(/\b(\w)/g);
var invoice_num = acr.join('');
var date = GetTodayDate();
var invoice_num = invoice_num+'_'+date;
var theUnits = {};
$('.unit').each(function() {
theUnits.unitQuantity += $(this).val()+',';
theUnits.unitPrice += $(this).attr("price")+',';
theUnits.unitName += $(this).attr("unitname")+',';
theUnits.unitCode += $(this).attr("unitcode")+',';
});
google.script.run.doReplace(client_name,client_phone,client_email,invoice_num,date,theUnits);
}
});
function GetTodayDate() {
var tdate = new Date();
var dd = tdate.getDate(); //yields day
var MM = tdate.getMonth(); //yields month
var yyyy = tdate.getFullYear(); //yields year
var currentDate= dd + "-" +( MM+1) + "-" + yyyy;
return currentDate;
}
</script>
</body>
</html>
Heres the GS file
/**
* #NotOnlyCurrentDoc
*
* The above comment directs Apps Script to limit the scope of file
* access for this add-on. It specifies that this add-on will only
* attempt to read or modify the files in which the add-on is used,
* and not all of the user's files. The authorization request message
* presented to users will reflect this limited scope.
*/
/**
* Creates a menu entry in the Google Docs UI when the document is opened.
* This method is only used by the regular add-on, and is never called by
* the mobile add-on version.
*
* #param {object} e The event parameter for a simple onOpen trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode.
*/
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'showSidebar')
.addToUi();
}
/**
* Runs when the add-on is installed.
* This method is only used by the regular add-on, and is never called by
* the mobile add-on version.
*
* #param {object} e The event parameter for a simple onInstall trigger. To
* determine which authorization mode (ScriptApp.AuthMode) the trigger is
* running in, inspect e.authMode. (In practice, onInstall triggers always
* run in AuthMode.FULL, but onOpen triggers may be AuthMode.LIMITED or
* AuthMode.NONE.)
*/
function onInstall(e) {
onOpen(e);
}
/**
* Opens a sidebar in the document containing the add-on's user interface.
* This method is only used by the regular add-on, and is never called by
* the mobile add-on version.
*/
function showSidebar() {
var ui = HtmlService.createHtmlOutputFromFile('sidebar')
.setTitle('Infinity Invoice Generator');
DocumentApp.getUi().showSidebar(ui);
}
/**
* Gets the text the user has selected. If there is no selection,
* this function displays an error message.
*
* #return {Array.<string>} The selected text.
*/
function getSheetData(furnType) {
var ss = SpreadsheetApp.openById('1jLpJ35Szy6R3DDVVuOmi0Cw462ukI2DWzHG6VxYWd1g');
var sheets = ss.getSheets();
if(furnType == 0) {
var ranges = [sheets[0].getRange('A2:A11').getValues(), sheets[0].getRange('B2:B11').getValues(), sheets[0].getRange('H2:H11').getValues()];
}
if(furnType == 1) {
var ranges = [sheets[1].getRange('A2:A10').getValues(), sheets[1].getRange('B2:B10').getValues(), sheets[1].getRange('H2:H10').getValues()];
}
return ranges;
}
function doReplace(client_name,client_phone,client_email,invoice_num,date,theUnits) {
var body = DocumentApp.getActiveDocument().getBody();
var tables = body.getTables();
var table = tables[2];
var unitQuantity = theUnits.unitQuantity.split(',');
var unitCode = theUnits.unitCode.split(',');
var unitName = theUnits.unitName.split(',');
var unitPrice = theUnits.unitPrice.split(',');
var style1 = {};
style1[DocumentApp.Attribute.FONT_FAMILY] = 'Arial';
style1[DocumentApp.Attribute.FONT_SIZE] = 12;
style1[DocumentApp.Attribute.BOLD] = true;
style1[DocumentApp.Attribute.BACKGROUND_COLOR] = '#D9D9D9';
style1[DocumentApp.Attribute.FOREGROUND_COLOR] = '#000000';
var style2 = {};
style2[DocumentApp.Attribute.FONT_FAMILY] = 'Arial';
style2[DocumentApp.Attribute.FONT_SIZE] = 12;
style2[DocumentApp.Attribute.BOLD] = false;
style2[DocumentApp.Attribute.BACKGROUND_COLOR] = '#F3F3F3';
style2[DocumentApp.Attribute.HORIZONTAL_ALIGNMENT] = 'CENTER';
style2[DocumentApp.Attribute.FOREGROUND_COLOR] = '#000000';
var i;
for (i = 0; i < unitQuantity.length; i++) {
var tr = table.appendTableRow();
var td = tr.appendTableCell(''+unitCode[i]+'').setAttributes(style1);
var td = tr.appendTableCell(''+unitName[i]+'').setAttributes(style2);
var td = tr.appendTableCell(''+unitPrice[i]+'').setAttributes(style2);
var td = tr.appendTableCell(''+unitQuantity[i]+'').setAttributes(style2);
}
body.replaceText("{{ClientName}}", client_name);
body.replaceText("{{ClientPhone}}", client_phone);
body.replaceText("{{ClientEmail}}", client_email);
body.replaceText("{{InvoiceNumber}}", invoice_num);
body.replaceText("{{CurrentDate}}", date);
}
Proble is with replaceIt function, specifically the way you are building theUnits object. Initialize each property within object with empty string and that will fix the issue:
function replaceIt() {
var client_name = $('#client_name').val();
var client_phone = $('#client_phone').val();
var client_email = $('#client_email').val();
var acr = client_name.match(/\b(\w)/g);
var invoice_num = acr.join('');
var date = GetTodayDate();
var invoice_num = invoice_num + '_' + date;
var theUnits = {
unitQuantity: "",
unitPrice: "",
unitName: "",
unitCode: ""
};
$('.unit').each(function() {
theUnits.unitQuantity += $(this).val() + ',';
theUnits.unitPrice += $(this).attr("price") + ',';
theUnits.unitName += $(this).attr("unitname") + ',';
theUnits.unitCode += $(this).attr("unitcode") + ',';
});
google.script.run.doReplace(client_name, client_phone, client_email, invoice_num, date, theUnits);
}

How do I display an unordered list in JavaScript?

I have to display a text field and a button “Generate times table”. When the user enters an integer from 1 to 9 and clicks the button, two duplicate times tables will appear side-by-side. However, the times tables on the right needs to be presented in an unordered list style.
When the user enters another number and click the button, the old times tables disappear and the new times tables will be generated accordingly.
I have managed to display one times tables set, however the second version (unordered list) won't display adjacent to it.
<html>
<head>
</head>
<body>
Enter integer:<input onClick="stopCounterAnimation()" id="number"
type="text">
<button onclick="startCounterAnimation()">Start Animation</button>
<button onclick="stopCounterAnimation()">Stop Animation</button>
<br/><br/>
<span id="counter"></span>
<script>
var counter = 0;
var counterSchedule;
var input = document.getElementById("number");
var counterSpan = document.getElementById("counter");
function startCounterAnimation() {
counterSchedule = setInterval(showCounter, 1000);
}
function showCounter() {
if (~~input.value) {
if (counter >= 9)
counter = 0;
counter++;
counterSpan.innerHTML = input.value + " X " + counter + " = "
+ eval(input.value + " * " + counter);
}
}
function stopCounterAnimation() {
clearInterval(counterSchedule);
input.value = "";
counter = 0;
}
</script>
</body>
</html>

How can i get a variable to change number depending on what the user has eneterd

So i am creating a simple dice game which you enter the guess of the dice roll and amount you wish to bet. I cant seem to figure out how i can get my balance to change when a user enters a number.
My code:
//declaring global variables
var diceroll = 0;
var balance = 1000;
var stake = 0;
var guess = 0;
while (balance <= 0) {
var guess = document.getElementById("guess").nodeValue;
var stake = document.getElementById("stake").nodeValue;
var diceroll = roll();
if (guess === diceroll) {
balance = balance + (stake * 5);
} else {
balance = balance - stake;
}
}
//Rolling the dice
function roll() {
"use strict";
diceroll = Math.floor(Math.random() * 6 + 1);
alert(diceroll);
}
//Display balance
document.getElementById("balance").innerHTML = balance;
<!DOCTYPE html>
<html>
<head>
<title>Dice-Game: GL HF</title>
</head>
<body>
<p id="balance"></p>
<form action="#">
Enter guess:
<input type="text" id="guess">
<br>Enter stake:
<input type="number" id="stake" name="stake">
<br>
<input type="button" name="play" onclick="roll()" value="PLAY!">
<br>
</form>
<script src="Dice.js" type="text/javascript"></script>
</body>
</html>
A few things:
The way I looked at it, your while loop wasn't doing anything. The whole balance calculation wasn't even part of your roll function. I removed it altogether.
You were using .nodeValue to get your guess and stake input values, which is deprecated. It should just be .value. Those two variables were returning null.
Your balance update wasn't being called with your function either, so it wouldn't have updated on the page even if the balance was actually updating.
Your declaration of global variables was a bit redundant and unnecessary.
//declaring global variables
var balance = 1000;
//Rolling the dice
function roll() {
var guess = document.getElementById("guess").value;
var stake = document.getElementById("stake").value;
"use strict";
var diceroll = Math.floor(Math.random() * 6 + 1);
alert(diceroll);
if(guess == diceroll) {
balance = balance + (stake * 5);
} else {
balance = balance - stake;
}
//Update display balance
document.getElementById("balance").innerHTML = balance;
}
//Initial display balance
document.getElementById("balance").innerHTML = balance;
Put
//Display balance
document.getElementById("balance").innerHTML = balance;
inside of the loop.
I also don't think your while condition is correct..
Try this,
balance = parseInt(balance) - parseInt(stake);

How to create simple javascript/jquery client side captcha?

How to create simple javascript/jquery client side captcha?
Why don't you use reCAPTCHA ? It's free, very efficient, and provides accessibility functionnalities.
It can be done with HTML and a simple JavaScript code. Have a look at this:
function Captcha(){
var alpha = new Array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'0','1','2','3','4','5','6','7','8','9');
var i;
for (i=0;i<6;i++){
var a = alpha[Math.floor(Math.random() * alpha.length)];
var b = alpha[Math.floor(Math.random() * alpha.length)];
var c = alpha[Math.floor(Math.random() * alpha.length)];
var d = alpha[Math.floor(Math.random() * alpha.length)];
var e = alpha[Math.floor(Math.random() * alpha.length)];
var f = alpha[Math.floor(Math.random() * alpha.length)];
var g = alpha[Math.floor(Math.random() * alpha.length)];
}
var code = a + ' ' + b + ' ' + ' ' + c + ' ' + d + ' ' + e + ' '+ f + ' ' + g;
document.getElementById("mainCaptcha").innerHTML = code
document.getElementById("mainCaptcha").value = code
}
function ValidCaptcha(){
var string1 = removeSpaces(document.getElementById('mainCaptcha').value);
var string2 = removeSpaces(document.getElementById('txtInput').value);
if (string1 == string2){
return true;
}else{
return false;
}
}
function removeSpaces(string){
return string.split(' ').join('');
}
.capt{
background-color:grey;
width: 300px;
height:100px;
}
#mainCaptcha{
position: relative;
left : 60px;
top: 5px;
}
#refresh{
position:relative;
left:230px;
width:30px;
height:30px;
bottom:45px;
background-image: url(rpt.jpg);
}
#txtInput, #Button1{
position: relative;
left:40px;
bottom: 40px;
}
<link rel="stylesheet" type="text/css" href="estilo.css" />
<script type="text/javascript" src="script.js"></script>
<body onload="Captcha();">
<div class="capt">
<h2 type="text" id="mainCaptcha"></h2>
<p><input type="button" id="refresh" onclick="Captcha();"/></p> <input type="text" id="txtInput"/>
<input id="Button1" type="button" value="Check" onclick="alert(ValidCaptcha());"/>
</div>
</body>
here you are ;)
var captchaText;
$(function() {
var pre = $('#captcha');
captchaText = pre.text();
pre.text('');
var lines = ['', '', '', '', '']
for (var ixLetter = 0; ixLetter < captchaText.length; ixLetter++) {
var letter = captchaText.substr(ixLetter, 1);
var letterLines = letters[letter];
for (var ix = 0; ix < 5; ix++) {
lines[ix] = lines[ix] + ' ' + letterLines[ix];
}
}
for (var ix = 0; ix < 5; ix++) {
pre.append(lines[ix] + '\n');
}
});
function check() {
if ($('#captchaCheck').val() == captchaText) {
alert('you are probably human');
} else {
alert('you probably made a mistake. Don\'t worry. To err is also human.');
}
}
var letters = {
h: [
'HH HH',
'HH HH',
'HHHHHH',
'HH HH',
'HH HH'
],
i: [
'II',
'II',
'II',
'II',
'II'
]
// etc
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<pre id="captcha">hi</pre> Please type what you see: <input id="captchaCheck" /> <input type="button" value="Check" onclick="check()" />
I agree with all the comments that client side captcha is fundamentally flawed, and I don't know know tough the hurdles have to be to mitigate any amount of spam...
To get an impression of what you're up against, though, check xRumer and GSA in action: YouTube: xEvil vs GSA Captcha Breaker
The software is also able to gather and decipher artificial intelligence such as security questions (i.e. what is 2+2?) often used by forums upon registration. Since the latest version of XRumer, the software is capable of collecting such security questions from multiple sources and is much more effective in defeating them.
wikipedia.org/wiki/XRumer
References:
How do spambots work? - Webmasters Stack Exchange (2010!)
BotDetect: Pure JavaScript CAPTCHA Validation Problems
So, caveats aside, here's a trivial alternative using HTML5 validation that's probably as ineffective as the other posts here! Presumably spambots would just add formnovalidate before submission, and would identify a honeypot field.
<form class="input">
<label for="number" class="title">
What is three plus four?
</label>
<br>
<input
name="number"
required="required"
pattern="(7|seven)"
oninvalid="this.setCustomValidity('Sorry, please enter the correct answer')"
oninput="this.setCustomValidity('')"
>
<!-- Bonus honeypot field, hidden from the user. The server should discard this response if it contains any input -->
<input name="decoy" style="display: none;" />
<input type="submit">
</form>
Client-Side captchas do not provide the protection a server-generated captcha would because the server can not check if the solved captcha is solved correctly.
That is not possible.
You could create something that looks like a CAPTCHA, but it would only run when it's not needed, i.e. when the page is shown in a browser. When it's needed it won't run, as the program trying to break in won't run the client side script.
function Captcha(){
var alpha = new Array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'0','1','2','3','4','5','6','7','8','9');
var i;
for (i=0;i<6;i++){
var a = alpha[Math.floor(Math.random() * alpha.length)];
var b = alpha[Math.floor(Math.random() * alpha.length)];
var c = alpha[Math.floor(Math.random() * alpha.length)];
var d = alpha[Math.floor(Math.random() * alpha.length)];
var e = alpha[Math.floor(Math.random() * alpha.length)];
var f = alpha[Math.floor(Math.random() * alpha.length)];
var g = alpha[Math.floor(Math.random() * alpha.length)];
}
var code = a + ' ' + b + ' ' + ' ' + c + ' ' + d + ' ' + e + ' '+ f + ' ' + g;
document.getElementById("mainCaptcha").innerHTML = code
document.getElementById("mainCaptcha").value = code
}
function ValidCaptcha(){
var string1 = removeSpaces(document.getElementById('mainCaptcha').value);
var string2 = removeSpaces(document.getElementById('txtInput').value);
if (string1 == string2){
return true;
}else{
return false;
}
}
function removeSpaces(string){
return string.split(' ').join('');
}
<h1 This works pretty well. </h1>
if your purpose is to simply deflect most bots, you might be able to get away with a simple script that chooses 2 numbers at random and asks user to add them.
Try this:
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script>
/* created dynamically by server when preparing the page */
// f function is on server and not known by bot and is random per result
// maybe bcrypt with a unique salt per request
var encodedResult = "f('X')";
var serverRenderedPositionSequence = [
[0,0],
[10,10],
[20,20],
[30,30],
[40,40],
[50,50],
[60,60],
[70,70],
[80,80],
[90,90],
[100,100],
[100,100],
[100,100],
[100,100],
[100,100],
[100,0],
[90,10],
[80,20],
[70,30],
[60,40],
[50,50],
[40,60],
[30,70],
[20,80],
[10,90],
[0,100]
];
window.index = 0;
window.move=function(but){
but.style.left = (serverRenderedPositionSequence[window.index][0]+"px");
but.style.top = (serverRenderedPositionSequence[window.index][1]+"px");
window.index++;
if(window.index<serverRenderedPositionSequence.length)
{
setTimeout(function(){
window.move(but);
},125);
}
}
window.onload=function(){
var but = document.getElementById("decoy-button");
window.index=0;
window.move(but);
};
function post()
{
// do something with
var xhrData= {
encodedResult:encodedResult,
result:document.getElementById('result').value
};
var postXhr=function(){ /* HTTP POST */}
// server checks if decoded "encoded result" is equal to the result (X here)
postXhr(xhrData);
}
</script>
</head>
<body>
<input id="result" type="text" value="Enter the message you see" />
<input id="test" onclick="post()" type="button" value="Post" />
<input id="decoy-button" type="button" value=" click me bot" style="width:0px;padding:0;position:absolute; left:0px; top:0px;" />
<input id="secret" type="button" onclick="window.index=0;move( document.getElementById('decoy-button'))" value="repeat sequence" />
</body>
</html>
Then the server would only use bcrypt to check if result is true.
Since the pattern matching requires dynamically observing the screen, it would cause some bots to fail. These would fail:
bots that click to post button immediately
bots that don't run javascript
bots that mis-click the decoy buttons
bots that can't understand the leap between coordinates in the sequence
for example when drawing X, it leaps from bot-right to top-right effectively drawing a vertical line on imaginary plane which may trick some bots easily
bots that use OCR with "fixed" screenshot timings
To further obfuscate any "canvas" hack, the server could add quick random bursts (with decreased latency between leaps) into the sequence such that resulting image would look unrecognizable but human will see what it writes between those random bursts.
To add one more depth, you can ask a math problem like
1+1
instead of just string.
But, when a bot memoizes the encoded result and re-sends it, the security is broken. So, have a "session" for client and not send any encoded result. Just the randomized sequence and check for result only by server-side. Of course the session also costs database time/space but at least website admin/editor does not see spam on the control panel of website.
please try this
<script type="text/javascript">
$('#submitcapt').click(function(){
var captval = "This will not do nothing";
$.ajax({
type : "POST",
url : "externals/reCaptcha/ajaxaction.php",
data : {loadval:captval},
success : function( msg ) {
if(msg=="1"){
}else{
}
}
})
});
</script>
In ajaxaction.php please put the following code
<?php
session_start();
$val=$_POST['loadval'];
$message= $_SESSION['random_number'];
if($val==$message) {
echo "1";
}else{
echo "2";
}
?>
$(document).ready(function() {
DrawCaptcha();
});
function refreshCap() {
DrawCaptcha();
}
function DrawCaptcha() {
var a = Math.ceil(Math.random() * 10) + '';
var b = Math.ceil(Math.random() * 10) + '';
var c = Math.ceil(Math.random() * 10) + '';
var d = Math.ceil(Math.random() * 10) + '';
var e = Math.ceil(Math.random() * 10) + '';
var f = Math.ceil(Math.random() * 10) + '';
var g = Math.ceil(Math.random() * 10) + '';
var code = a + ' ' + b + ' ' + ' ' + c + ' ' + d + ' ' + e + ' ' + f + ' ' + g;
var test = document.getElementById("ContentPlaceHolder1_txtCapcha").value = code;
alert(test);
}
function ValidCaptcha() {
var str1 = removeSpaces(document.getElementById('ContentPlaceHolder1_txtCapcha').value);
var str2 = removeSpaces(document.getElementById('ContentPlaceHolder1_txtinputCapcha').value);
if (str1 != str2) {
alert("Properly enter the Security code.");
document.getElementById('ContentPlaceHolder1_txtinputCapcha').focus() return false;
}
}
function removeSpaces(string) {
return string.split(' ').join('');
}

Categories