I am having trouble dynamically updating values in a jQuery-generated table.
I inserted a button next to values that the contents of adjacent <td> are calculated from, with the intent that the .click(function(){ prompt("...","...")} would not only update the value of interest (hours, avgCustomers, avgPurchase), but also recalculate the dependent <td>.
The contents of the table are calculated on $(document).ready from an object constructor, using arrays for the input values (see Shops.js # http://github.com/jacobshillman/Week2A2.git, and/or below). In the git, 'index.html' is the page of interest.
A click event updates the value displayed, but I haven't been able to figure out how to get the rest of the table to recalculate based on the new value.
//Donut Shop Constructor
function Shop (loc, hours, minCust, maxCust, avgDonCust, avgCust, donHr, donDay) {
this.loc = loc;
this.hours = hours;
console.log(this.loc + ", Hours: " + this.hours);
this.minCust = minCust;
this.maxCust = maxCust;
this.avgDonCust = avgDonCust;
//Random sample of average customers per hour:
this.avgCust = getCustpHr(this.hours, this.minCust, this.maxCust);
console.log(this.loc + ", Customers per hour: " + this.avgCust);
//Donuts to bake per hour:
this.donHr = getDon(this.avgCust, this.avgDonCust)
console.log(this.loc + ", Donuts to bake per hour: " + this.donHr);
//Donuts to bake per day:
this.donDay = getSum (this.donHr)
console.log(this.loc + ", Donuts to bake per day: " + this.donDay);
};
//Donut Shops declaration:
var Shops = [5];
Shops[0] = new Shop("Downtown", 8, 8, 43, 4.5);
Shops[1] = new Shop("Capitol Hill", 24, 4, 37, 2);
Shops[2] = new Shop("South Lake Union", 10, 9, 23, 6.33);
Shops[3] = new Shop("Wedgewood", 7, 2, 28, 1.25);
Shops[4] = new Shop("Ballard", 10, 8, 58, 3.75);
//Populate index.html with Donut shops stats:
$(document).ready(function(){
$.each(Shops, function(){
$row = $('<tr></tr>');
$('#shopGrid tbody').append($row);
$row.append($('<td><ul>' + this.loc + '<li>Hours Open: ' +
'<span id="Hour">' + this.hours + '</span>'
+ '<input type="button" class="edit" value="EDIT">' + '</li>'
+ '<li>Average Purchase:' +
'<span id="Purch">' + this.avgDonCust + '</span>'
+ '<input type="button" class="edit" value="EDIT">' + '</li>'
+ '<li>Store Traffic:' +
'<span id="Traffic">' + this.minCust + '-' + this.maxCust + '</span>'
+ '<input type="button" class="edit" value="EDIT">' + '</li></ul>'))
//.slice() is inserted to control formatting:
$row.append($('<td>' + this.avgCust.slice(0,8) + '<br>'
+ this.avgCust.slice(8,16) + '<br>'
+ this.avgCust.slice(16) +'</td>'));
$row.append($('<td>' + this.donHr.slice(0,8) + '<br>'
+ this.donHr.slice(8,16) + '<br>'
+ this.donHr.slice(16) + '</td>'));
$row.append($('<td>' + this.donDay + '</td>'));
});
//Shop stats EDIT buttons:
//If else for EDIT button fX:
$(".edit").click(function(){
var test = $('#Hour').text();
console.log(test);
var newHours = prompt("Enter new number between 0 and 24", "New Hours");
if (newHours <= 24) {
$('#Hour').text(newHours);
test = newHours
}else{
alert('Must be a number between 0 to 24.')
}
console.log(test);
console.log(document.getElementById('Hour'));
});
/*
$(".edit").click(function(){
if ($('#Hour')) {
var newHours = prompt("Enter new number between 0 and 24", "New Hours");
if (newHours <= 24) {
$('#Hour').value(newHours);
}else{
alert('Must be a number between 0 to 24.')
}
}else if ($('#Purch')) {
var newVal = prompt("Enter new value:", "Enter number");
$(this.avgDonCust).val(newVal);
}else if ($('#Traffic')) {
var newVal = prompt("Enter first value:", "Enter number");
$(this.minCust).value(newVal)
var val2 = prompt("Enter second value", "Enter number");
$(this.maxCust).value(Val2);
};
});
*/
/*
//Switch for EDIT buttons fX:
$(".edit").click(function (){
switch (n){
case $('#Hour')
var newHours = prompt("Enter new number between 0 and 24", "New Hours");
if newHours <= 24 {
$(this.hours).val(newHours);
}else{
alert('Must be a number between 0 to 24.');
break;
case $('Purch'):
var newVal = prompt("Enter new value:", "Enter number");
$(this.avgDonCust).val(newVal);
case $('#Traffic'):
var newVal = prompt("Enter first value:", "Enter number");
$(this.minCust).val(newVal);
function() {
var val2 = prompt("Enter second value", "Enter number");
$(this.maxCust).val(Val2);
};
break;
}
*/
});
The code commented out and in console.log() are my attempts at troubleshooting and getting the table to recalculate.
Best I can figure is that I need to update the corresponding value in the 2-D array Shops - Shops[this][[1] to update the "Hours Open:" of any given shop, then triggering the calculations again, without reloading the page.
I tried in an earlier (failed) attempt to use objects instead of arrays, but descended into defining jQuery var=s hell because I had to hand-jam all the object names into html and jQuery. This attempt can be seen in the 'Legacy' directory in the .git, above.
I've seen updateTo() and recompute() advice on Stack Overflow, but I'm not sure whether/how I'd be able to integrate them.
Any input is greatly appreciated! I'm not opposed to scrapping things and starting from scratch: this is a learning exercise.
I'm not able to make a working example as I'm pressed for time, but I can give you a few suggestions. These suggestions are only for if you're not coding a reusable library.
I'd create properties (functions) that recompute other properties and/or the html.
var _total = 0, _tax = 0, _grand = 0;
function totalChange() {
$('#prop1').val(_prop1);
tax(_total * .08)
_.debounce(_grand);
}
function total(val) {
if(val) {
_total = val;
totalChange();
}
return _total;
}
Then you can create a getter and setter for each of your properties that sets html or other properties.
I would actually recommend emberjs or angular or backbone as they provide the exact functionality you're looking for.
I was bored today and created a jsfiddle for you using ember. Let me know if you have an questions.
Related
EDIT#2:
I've decided to try and batch the copy-paste operations that run after all the emails are fired. In previous posted code, in the last lines of every loop, I copied and pasted the items from each purchase order ("Email" sheet) to another ("AsignRec"). Now, what I want to do is store the items from "Email" sheet in every loop to a Javascript array, and paste all together into "AsignRec" at the end, just once.
However, I'm still not doing it right. I'm stuck at the final pasting/setValues(). I believe the array is correctly formed, as it has a length of 49, which is the number of unique SKUs to be sent out to suppliers. Still, at the setValues([OCitems]) line 185, I get the error "Incorrect range height, was 1 but should be 49 (line 185, file "TestArrayMultiple4")".
I assume this means the destination/output range is not the same size as array/input (called OCitems). I don't see why though, since I defined the length of the output range using OCitems.length. I am missing something, and not sure what.
This is the important bit of the code, and full code below. Same GDocs link as before, script file TestArrayMultiple4, lines 160-185.
https://docs.google.com/spreadsheets/d/1yzvMTh0VYhRhiexNzQPIjTwz1FCMq4XnbpGvCF1FYu8/edit#gid=436022027
/// Get Range we want to change to creating Javascript array and paste at end only
var OcNoHeader = sheet.getRange("B9:J" + MaxTableRow).getValues(); // get items to send to supplier from "Email Sheet"
// if supplier number is 1, create array "OCitems" by storing OcNoHeader. If not supplier #1, then append to existing array "OCitems"
if(y == 1){
var OCitems = OcNoHeader}
else
{for(j=0;j<OcNoHeader.length;j++){
OCitems.push(OcNoHeader[j]);
}}
Logger.log(OCitems);
Logger.log("OCitems length = " + OCitems.length)
debugger;
i++; // after firing email, y+1 to go to next supplier
Logger.log("i++ IF =" + 1);
Logger.log("new i ELSE =" + 1)
debugger;
} // only do while x = max number of suppliers reached
while (i < x);
sheet3.getRange(3, 3, OCitems.length, 9).setValues([OCitems]); // paste operation, NumRows set equal to length of array
=========================
EDIT#1: Worked on improving performance using getValues of several cells instead of doing individual getValue() several times over. Unfortunately, this hasn't reduced the execution time in a stable or noticeable manner (sometimes it finished before 6min, sometimes not).
Posting code below (you can access it in script file "TestArrayMultiple2" in new Sheet shared below):
Using Execution transcript, I see that although the execution time of the getValue() lines that were previously taking very long has basically been reduced to zero, other lines of code are now taking more time and killing the gain achieved from the batching other getValue().
There are still 4-5 "individual" getValue() on specific cells (much less than before), but I don't understand why they would take so long. So it seems even if I removed the remaining "individual" getValue(), if only one remained, it would take even longer.
Seems to me it has something to do with caching (and I'm sure I don't fully understand this concept), for the following reasons:
1) It is always the first getValue() in the loop which takes the longest.
2) I tried to go down a different route, by changing the code for the copy/paste operation which occurrs after all emails are sent (line 150 in "TestArrayMultiple2" script file). I basically try to create an array which gets fed with more data at every loop (append/push method) but doesn't paste within every loop - the idea is to paste all the data at the end, after finishing looping. I still don't have it right (this second script file is the last one, "TestArrayMultiple3"), but I can see the emails get fired off much faster.
Once again, your help would be much appreciated.
> // version with 1 getDataRange array which stores for supplier ID, name, email, MaxTableRow for PO email, email subject all from Dashboard sheet
function TestArrayMultiple2() {
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var sheet = ss.getSheetByName("Email");
var sheet1 = ss.getSheetByName("Pedido email");
var sheet2 = ss.getSheetByName("Dashboard");
var sheet3 = ss.getSheetByName("AsignRec");
var sheet4 = ss.getSheetByName("ListadoProductos");
var sheet5 = ss.getSheetByName("Registro-Consolid");
var sheet6 = ss.getSheetByName("Registro-Unico");
var x = ss.getSheetByName("Dashboard").getRange("C4").getValue();
Logger.log("x = " + x)
var offsetV = 4; // number of rows of offset for email status to be inserted in Dashboard sheet
var OffSetColProv = 1; // column in Dashboard sheet with supplier name
var OffsetColMaxPOrows = 3; // Number of unique SKUs or rows in PO. Replace MaxTableRow formula in Email Sheet
var OffSetColPzas = 4; // column in Dashboard sheet with number of items in supplier purchase order.
var OffSetColEmail = 5; // column in Dashboard sheet with supplier email
var OffSetColCC = 13; // column in Dashboard sheet with supplier email CC
var OffSetColSubject = 14; // column in Dashboard sheet with supplier email Subject
var colStatus = 11; // column in Dashboard sheet where send status of email inserted -----> LEAVE AS IS FOR NOW, not an offset, is fixed, col. K = 11
var OffsetEmailRows = 8 // number of rows in Email sheet before the items in PO are shown
var ProvNumEmail = sheet.getRange(1,2); // Supplier number in email sheet used to refresh products in purchase order email via FILTER formula
var StatusRange = sheet2.getRange("K5:K100");
Logger.log("StatusRange = " + StatusRange)
var currentTime = new Date();
var timestamp = Utilities.formatDate(currentTime,'GMT-0600','dd/MM/yyyy HH:mm:ss');
Logger.log("timestamp = " + timestamp);
var ProvArray = sheet2.getRange("E5:S100");
var DashValues = ProvArray.getValues();
i = 0;
do {
var y = DashValues[i][0];
Logger.log("y = " + y)
ProvNumEmail.setValue(y); // set value of next supplier in Email sheet to load next purchase order products
// emails var here in order to update email value in IF email = ERROR condition and skip to else
var Prov = DashValues[i][OffSetColProv];
Logger.log("Prov = " + Prov);
var EmailSubject = DashValues[i][OffSetColSubject];
Logger.log("EmailSubject = " + EmailSubject)
var MaxTableRow = DashValues[i][OffsetColMaxPOrows] + OffsetEmailRows;
Logger.log("MaxTableRow = " + MaxTableRow)
var EmailTo = DashValues[i][OffSetColEmail];
Logger.log("EmailTo = " + EmailTo)
var EmailCC = DashValues[i][OffSetColCC];
Logger.log("EmailCC = " + EmailCC)
var Piezas = DashValues[i][OffSetColPzas];
Logger.log("Piezas = " + Piezas)
SpreadsheetApp.flush();
var name = "Petsy Compras - Juan Carlos León";
var ReplyToEmail = "compras#petsy.mx";
var email = EmailTo;
var subject = EmailSubject;
var name = name;
var replyTo = ReplyToEmail;
var Emailcc = EmailCC;
var schedRange = sheet.getRange("B7:J"+MaxTableRow);
var body = '<div>';
body += "Estimados," +'<br>' + '<br>';
body += "Envío la orden de compra, por un total de " + '<b>' + Piezas + " piezas." + '</b>' +'<br>' + '<br>';
body += "Favor de confirmar las existencias lo más rápidamente posible, dentro del mismo correo y"+ '<b><a style="color:#FF0000">'+ " enviar factura a: "+ '</a></b>' + "facturasproveedores#petsy.mx." +'<br>' + '<br>';
body += "Al dar " +'<b><a style="color:#FF0000"> '+ "RESPONDER A TODOS" + '</a></b>' +" la tabla con los productos pedidos se hace editable: favor de marcar por cada item si será faltante." +'<br>' + '<br>';
body += "Cualquier duda avísenme por favor." +'<br>' + '<br>';
body += "Un saludo" +'<br>' + '<br>';
body += '<b>' + "Juan Carlos León" + '<b>' + '<br>';
body += "Petsy Compras"+'<br>';
body += "Mapa aquí: "+'<br>';
body += "Fijo directo 1: (55) 68 12 07 97 / Fijo directo 2: (55) 68 12 07 99 / Cel y Whatsapp: 55 32 23 57 17"+'<br>' + '<br>';
body += "" +'<br>' + '<br>';
body += getHtmlTable(schedRange);
body += '</div>';
// variables for error email
var emailERR = 'oscialom#petsy.mx'
var subjectERR = 'ERROR ENVIO OC' + ' // ' + Prov + ' ' + timestamp
if(email == 'ERROR' || MaxTableRow == 0) // skip condition to go begin loop with y+1
{
// if above skip condition is true, y+1 to move to next purchase order
Logger.log("y = " + y);
Logger.log("IF");
sheet2.getRange(y + offsetV,colStatus).setValue('NOT_SENT'); // set email send status next to supplier in Dashboard sheet
{
GmailApp.sendEmail(emailERR, subjectERR, "Requires HTML",
{
'name':name,
'replyTo':replyTo,
'htmlBody':'',
'cc':''});
}
i++;
Logger.log("new i IF =" + 1);
continue
}
else
{
// if skip condition is false, fire current supplier purchase order email email
Logger.log("i = " + i);
Logger.log("y = " + y);
Logger.log("ELSE");
GmailApp.sendEmail(email, subject, "Requires HTML",
{
'name':name,
'replyTo':replyTo,
'htmlBody':body,
'cc':EmailCC});
}
sheet2.getRange(y + offsetV,colStatus).setValue('OK'); // set email send status next to supplier in Dashboard sheet
// START copy-paste Asign-Rec
var MaxTableRowASIGN = sheet3.getRange("A1").getValue();
Logger.log("MaxTableRowASIGN = " + MaxTableRowASIGN)
/// Get Range we want to change to creating Javascript array and paste at end only
debugger; // stop debugger at this point !! REMOVE OR PLACE AT CORRECT LINE IF USING DEBUGGER
var OcNoHeader = sheet.getRange("B9:J" + MaxTableRow);
var ConsolAsignRec = sheet3.getRange("B3:K" + MaxTableRow);
var ProvOC = sheet.getRange("B2").getValue();
Logger.log("ProvOC = " + ProvOC)
var MaxRowB = sheet3.getRange("C1").getValue() + 1;
Logger.log("MaxRowB = " + MaxRowB);
var NextRowB = MaxRowB + 1;
Logger.log("NextRowB = " + NextRowB);
OcNoHeader.copyTo(sheet3.getRange(MaxTableRowASIGN + 1,3),{contentsOnly:true});
var NumRowsProv = OcNoHeader.getNumRows();
var ProvOCcolumn = sheet3.getRange(MaxRowB, 2, NumRowsProv)
Logger.log("ProvOCcolumn = " + ProvOCcolumn);
ProvOCcolumn.setValue(ProvOC);
// END copy-paste Asign-Rec
i++; // after firing email, y+1 to go to next supplier
Logger.log("i++ IF =" + 1);
Logger.log("new i ELSE =" + 1)
} // only do while x = max number of suppliers reached
while (y<x);
// set y = 1 to reset value again after finishing loop
sheet.getRange(1,2).setValue(1); // reset ProvNumber = 1 to start again next time script is fired.
var EmailsSent = sheet2.getRange("C10").getValue(); // set values
Logger.log("EmailsSent = " + EmailsSent)
var EmailErrors = sheet2.getRange("C11").getValue();
Logger.log("EmailErrors = " + EmailErrors)
var MaxTableRowEND = sheet2.getRange("C9").getValue();
var schedRange = sheet2.getRange("E4:K" + MaxTableRowEND);
var emailEND = "oscialom#petsy.mx";
var subjectEND = 'OCs Inbound enviadas' + ' ' + timestamp + " (errores " + EmailErrors + " / enviados " + EmailsSent + ")";
var EmailCCEND = "";
var bodyEND = getHtmlTable(schedRange);
GmailApp.sendEmail(emailEND, subjectEND, "Requires HTML",
{
'name':name,
'replyTo':replyTo,
'htmlBody':bodyEND,
'cc':EmailCCEND});
StatusRange.clearContent();
/// START RecordTimestamp code
var Avals = sheet4.getRange("A1:A").getValues();
var lastrow1 = Avals.filter(String).length;
Logger.log('lastrow1 =' + lastrow1)
var Avals2 = sheet5.getRange("A1:A").getValues();
var lastrow2 = Avals2.filter(String).length;
Logger.log('lastrow2 =' + lastrow2)
sheet4.getRange("B2:B" + lastrow1).copyTo(sheet5.getRange(lastrow2 + 1, 1)) // copy order-items to Registro sheet, after last filled row
sheet4.getRange("K2:K" + lastrow1).copyTo(sheet5.getRange(lastrow2 + 1, 2)) // copy Prov1 to Registro sheet, after last filled row
var Avals3 = sheet5.getRange("C1:C").getValues();
var lastrow2c = Avals3.filter(String).length;
Logger.log('lastrow2c =' + lastrow2c);
if(lastrow2 == 1)
{ sheet5.getRange(lastrow2c + 1, 3, lastrow1 - 1).setValue(timestamp)
Logger.log('IF')
}
else
{
sheet5.getRange(lastrow2c + 1, 3, lastrow1 - 1).setValue(timestamp)
Logger.log('ELSE')
}
var MaxTableRowEMAIL = sheet6.getRange("G5").getValue()
var subject = "Items pedidos en OC automatizada " + timestamp
var email = "oscialom#petsy.mx";
var EmailCC = "";
var EmailBCC;
var name = "Petsy Compras";
var ReplyToEmail = "compras#petsy.mx"
var schedRange = sheet6.getRange("A1:C" + MaxTableRowEMAIL);
var body = getHtmlTable(schedRange);
{
GmailApp.sendEmail(email, subject, "Requires HTML",
{
'name':name,
'replyTo':ReplyToEmail,
'htmlBody':body,
'cc':''});
}
/// END RecordTimestamp code
Logger.log("MaxTableRowASIGN " + MaxTableRowASIGN);
var endtime = new Date();
Logger.log("timestamp end " + timestamp);
Logger.log("endtime " + Utilities.formatDate(endtime,'GMT-0600','dd/MM/yyyy HH:mm:ss'));
var scripttime = (endtime - currentTime);
Logger.log("scripttime original" + scripttime);
// strip the ms
scripttime /= 1000;
Logger.log("scripttime / 1000" + scripttime);
// get seconds (Original had 'round' which incorrectly counts 0:28, 0:29, 1:30 ... 1:59, 1:0)
var seconds = Math.round(scripttime % 60);
Logger.log("scripttime % 60" + scripttime);
// remove seconds from the date
scripttime = Math.floor(scripttime / 60);
Logger.log("scripttime / 60" + scripttime);
// Browser.msgBox("Script completado en " + seconds + " segundos",Browser.Buttons.OK_CANCEL); // removed MsgBox to measure real execution time
Logger.log("seconds " + seconds)
}
=====================
ORIGINAL POST
I wrote a Google script to automate the purchase order process to several suppliers. The process takes a list of products (from sheet ListaProductos), formats the product info into an email format (sheet "Email"), sends the emails, and does some copy/pasting into other sheets of the same spreadsheet. However, I always run into the execution time at around 75% of the script. I'm fairly new to this, have been reading up but frankly don't know what to try next.
I see the problem in this part of code:
do {
// read info from the sheet
range.getValue();
// more code here...
} // only do while x = max number of suppliers reached
while (y<x)
Operation getValue takes much time to run. Best practice is to use the whole range:
var data = sheet.getDataRange().getValuses();
and then use data as source for further calculations.
See more info here:
https://developers.google.com/apps-script/best_practices
Okay, that title will sound a bit crazy. I have an object, which I build from a bunch of inputs (from the user). I set them according to their value received, but sometimes they are not set at all, which makes them null. What I really want to do, it make an item generator for WoW. The items can have multiple attributes, which all look the same to the user. Here is my example:
+3 Agility
+5 Stamina
+10 Dodge
In theory, that should just grab my object's property name and key value, then output it in the same fashion. However, how do I setup that if-statement?
Here is what my current if-statement MADNESS looks like:
if(property == "agility") {
text = "+" + text + " Agility";
}
if(property == "stamina") {
text = "+" + text + " Stamina";
}
if(property == "dodge") {
text = "+" + text + " Dodge";
}
You get that point right? In WoW there are A TON of attributes, so it would suck that I would have to create an if-statement for each, because there are simply too many. It's basically repeating itself, but still using the property name all the way. Here is what my JSFiddle looks like: http://jsfiddle.net/pm2328hx/ so you can play with it yourself. Thanks!
EDIT: Oh by the way, what I want to do is something like this:
if(property == "agility" || property == "stamina" || ....) {
text = "+" + text + " " + THE_ABOVE_VARIABLE_WHICH_IS_TRUE;
}
Which is hacky as well. I definitely don't want that.
if(['agility','stamina','dodge'].indexOf(property) !== -1){
text = "+" + text + " " + property;
}
If you need the first letter capitalized :
if(['agility','stamina','dodge'].indexOf(property) !== -1){
text = "+" + text + " " + property.charAt(0).toUpperCase() + property.substr(1);
}
UPDATE per comment:
If you already have an array of all the attributes somewhere, use that instead
var myatts = [
'agility',
'stamina',
'dodge'
];
if(myatts.indexOf(property) !== -1){
text = "+" + text + " " + property.charAt(0).toUpperCase() + property.substr(1);
}
UPDATE per next comment:
If you already have an object with the attributes as keys, you can use Object.keys(), but be sure to also employ hasOwnProperty
var item = {};
item.attribute = {
agility:100,
stamina:200,
dodge:300
};
var property = "agility";
var text = "";
if(Object.keys(item.attribute).indexOf(property) !== -1){
if(item.attribute.hasOwnProperty(property)){
text = "+" + text + " " + property.charAt(0).toUpperCase() + property.substr(1);
}
}
Fiddle: http://jsfiddle.net/trex005/rk9j10bx/
UPDATE to answer intended question instead of asked question
How do I expand the following object into following string? Note: the attributes are dynamic.
Object:
var item = {};
item.attribute = {
agility:100,
stamina:200,
dodge:300
};
String:
+ 100 Agility + 200 Stamina + 300 Dodge
Answer:
var text = "";
for(var property in item.attribute){
if(item.attribute.hasOwnProperty(property)){
if(text.length > 0) text += " ";
text += "+ " + item.attribute[property] + " " + property.charAt(0).toUpperCase() + property.substr(1);
}
}
It's unclear how you're getting these values an storing them internally - but assuming you store them in a hash table:
properties = { stamina: 10,
agility: 45,
...
}
Then you could display it something like this:
var text = '';
for (var key in properties) {
// use hasOwnProperty to filter out keys from the Object.prototype
if (h.hasOwnProperty(k)) {
text = text + ' ' h[k] + ' ' + k + '<br/>';
}
}
After chat, code came out as follows:
var item = {};
item.name = "Thunderfury";
item.rarity = "legendary";
item.itemLevel = 80;
item.equip = "Binds when picked up";
item.unique = "Unique";
item.itemType = "Sword";
item.speed = 1.90;
item.slot = "One-handed";
item.damage = "36 - 68";
item.dps = 27.59;
item.attributes = {
agility:100,
stamina:200,
dodge:300
};
item.durability = 130;
item.chanceOnHit = "Blasts your enemy with lightning, dealing 209 Nature damage and then jumping to additional nearby enemies. Each jump reduces that victim's Nature resistance by 17. Affects 5 targets. Your primary target is also consumed by a cyclone, slowing its attack speed by 20% for 12 sec.";
item.levelRequirement = 60;
function build() {
box = $('<div id="box">'); //builds in memory
for (var key in item) {
if (item.hasOwnProperty(key)) {
if (key === 'attributes') {
for (var k in item.attributes) {
if (item.attributes.hasOwnProperty(k)) {
box.append('<span class="' + k + '">+' + item.attributes[k] + ' ' + k + '</span>');
}
}
} else {
box.append('<span id="' + key + '" class="' + item[key] + '">' + item[key] + '</span>');
}
}
}
$("#box").replaceWith(box);
}
build();
http://jsfiddle.net/gp0qfwfr/5/
Basically my script is supposed to:
Open a certain sheet
check a certain range of data for values below 60%
once it finds one, check the first row of that column to see if it says 'Sent'
If it doess, do nothing
If it doesn't, send jthe email message in the script with the value below 60% etc.
Then edit row one of that column with 'Sent'once it send messages for all of the values below 60% in that column. ( I haven't written this part yet.
It says it runs fine, but it doesn't send anything. I wrote most of this code from scratch and I'm kind of a beginner, so I'm wondering if I have errors that are keeping it from working. I am going to have this run on a timing trigger BTW.
If you would mind looking it over and giving me some feedback, I'd greatly appreciate it. I added the code below.
Happy Holidays!
Brandon
function sendEmail() {
var ss = SpreadsheetApp.openById('1CvK-ALbc-_GZwX4pqadBb67AVAou6euk55OE1axfbAk');
var sheet = ss.getSheets()[0]; // The first of the above spreadsheet
var range = sheet.getRange(3, 10, 40, 40); // Get 2D range (Starting Row (3) ,Starting Column (J), # Rows (40),# Colmuns (40))
var value = range.getValue(); // get values of all cells in that range
if (value < 0.6) { // if it it get values that are less than 60%
var editedsheet = value.getsheet(); // get sheet that value < 60% is in
var editedRow = value.getRow(); // get row that value < 60% is in
var column = value.getColumn(); // get column that value < 60% is in
var status = editedsheet.getRange(0, column).getValue() // check row 1 of that column and get value
if (status != 'Sent') { // if that value is sent, do nothing
}
else { // if the value isnt sent then...
var studentData = editedsheet.getRange(editedRow, 1, 1, 9).getValues(); // email message details
var message = 'Assessment score: ' + Math.round((value * 100) * 10) / 10 + ' %' +
'\nStudentId: ' + studentData[0][0] +
'\nName: ' + studentData[0][1] +
'\nHR: ' + studentData[0][2] +
'\nTeacher: ' + studentData[0][3] +
'\nGrade: ' + studentData[0][4] +
'\nRace: ' + studentData[0][5] +
'\nG: ' + studentData[0][6] +
'\nEd: ' + studentData[0][7] +
'\nAVG: ' + Math.round((studentData[0][8] * 100) * 10) / 10 + ' %';
var emailAddress = 'email address'; // email details
var subject = 'ALERT - Assessment score below 60% inputted.';
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
Here is a link to an example spreadsheet like the one I'm using.
Example Spreadsheet
There are some issues with your code. E.g. use .getValues() instead of .getValue() if you want to get the values of the range. Also you will have to loop through the values that are returned.
I think your code should look something like this:
NOTE: untested code !
function assessmentAlert() {
SpreadsheetApp.getActive().getSheets()
.forEach( function (s) {
var val = s.getDataRange().getValues();
//check to see if correct cells where found ==> check the log when the script finishes
var count = 0;
//start looping through the values, first loop = rows, second loop = columns
for (var i = 0, ilen = val.length; i < ilen; i++) {
for (var j = 0, jlen = val[0].length; j < jlen; j++) {
//conditions: column > 8, row > 2, value should be numeric and < 0.6 and the headerrow should not have 'Sent'
if (j > 8 && i >2 && !isNaN(parseFloat(val[i][j])) && val[i][j] < 0.6 && val[0][j] != 'Sent') {
count += 1
//if all conditions are met, send the email
var message = 'Assessment score: ' + Math.round((val[i][j] * 100) * 10) / 10 + ' %' +
'\nStudentId: ' + val[i][0] +
'\nName: ' + val[i][1] +
'\nHR: ' + val[i][2] +
'\nTeacher: ' + val[i][3] +
'\nGrade: ' + val[i][4] +
'\nRace: ' + val[i][5] +
'\nG: ' + val[i][6] +
'\nEd: ' + val[i][7] +
'\nAVG: ' + Math.round((val[i][8] * 100) * 10) / 10 + ' %';
var emailAddress = 'email#email.com'; // email details
var subject = 'ALERT - Assessment score below 60% inputted.';
MailApp.sendEmail(emailAddress, subject, message);
s.getRange(1, j+1).setValue('Sent').setFontColor('Red');
}
}
}
Logger.log('number of values found:' + count);
});
}
NOTE: the above code is untested. So I'll suggest you comment out the line MailApp.sendEmail and run the code and check the logger if the number of values found is correct.
I hope this helps ?
Your check for 'sent' is looking at a getRange(0,column). While the array it returns starts at 0, you should specify row 1 as getRange(1,column)
I searched and tried to use what I could find, but I'm stuck. I am attempting to create a script that scans a Google sheet (with about 30 sheets) for values that are newly inputted and are below 60%. I am then looking to have the script e-mail me the data that is in the 2nd column (Student's name), 4th column (teacher's name) of the current row of the data as well as the percentage (test score)that was below 60% (ex. Clarke,John Mrs. Brown 56.64%) . I am new to Java script but have been trying to learn.
Here is the script that I have so far. It finds the correct sheet without a problem. It will also email me as well (with the wrong message, but it works none the less). But there are certain things that I am stuck on. Like I know that I have to set up some sort of onEdit trigger so that it only sends the newly inputted score, but I don't know where to put it or how to do that. I also know that the dataRange code that I have seems wrong to me and the if statement also seems wrong. I am not sure how to specify "below 60%".
I am a technology teacher and set up a massive data system for our school using Google Drive. I want to have the master data sheet automatically email my principal whenever a student needs help on a specific assessment. Any help you could give me would be much appreciated!
Thank you guys so much for all of your time I love spending my time on here learning from everyone.
Brandon
Here is a link to an example of the data spreadsheet we use
Choppy Script Below
function sendEmail() {
var spreadsheet = SpreadsheetApp.openById('spreadsheet ID');
/// The ID of the data spreadshfeet
var sheet = spreadsheet.getSheets(); // gets all sheets
var startRow = 3; // Third row of data to process
var numRows = 40; // Number of rows to process
var dataRange = sheet.getRange(startRow, 10, numRows, 50); // Start row 3 column 10, and stop row 40, column 50
// Fetch values for each row in the Range.
var data = dataRange.getValues();
//Browser.msgBox(data)
for (i in data) {
var row = data[i];
if (dataRange.getValues() <= .60); {
var emailAddress = "my email";
var message = row[2]; // Second column
var subject = "ALERT - Assessment score below 60% inputted.";
MailApp.sendEmail(emailAddress, subject, message);
// Browser.msgBox(emailAddress)
}
}
}
function emailAlert(e) {
var range = e.range;
if (range.getColumn() >= 10) { // Only check column I and up
var editedSheet = e.source.getActiveSheet();
var editedRow = range.getRow();
var value = range.getValue();
if (value !== 'undefined') {
if (value < 0.6) {
var studentData = editedSheet.getRange(editedRow, 1, 1, 9).getValues();
Logger.log(
'StudentId: ' + studentData[0][0] +
'\n Name: ' + studentData[0][1] +
'\n HR: ' + studentData[0][2] +
'\n Teacher: ' + studentData[0][3] +
'\n Grade: ' + studentData[0][4] +
'\n Race: ' + studentData[0][5] +
'\n G: ' + studentData[0][6] +
'\n Ed: ' + studentData[0][7] +
'\n AVG: ' + studentData[0][8]);
var emailAddress = "brandon.mause#sleschool.org";
var message = Test
var subject = "ALERT - Assessment score below 60% inputted.";
MailApp.sendEmail(emailAddress, subject, message);
// Send email..
}
}
}
}
I added the new code above with the installable trigger active. I still can't get the email function to work properly. Any Ideas?
Thanks...
It sounds like your problem can be solved using the onEdit trigger. However, since you want to send an email you'll need to add the trigger manually because the function requires authorization to send emails.
Try this:
function assessmentOnEdit(e) {
var range = e.range;
if (range.getColumn() >= 10) { // Only check column I and up
var editedSheet = e.source.getActiveSheet();
var editedRow = range.getRow();
var value = range.getValue();
if (typeof value === 'number') {
if (value < 0.6) {
var studentData = editedSheet.getRange(editedRow, 1, 1, 9).getValues();
var message = 'Assessment score: ' + Math.round((value * 100) * 10) / 10 + ' %' +
'\nStudentId: ' + studentData[0][0] +
'\nName: ' + studentData[0][1] +
'\nHR: ' + studentData[0][2] +
'\nTeacher: ' + studentData[0][3] +
'\nGrade: ' + studentData[0][4] +
'\nRace: ' + studentData[0][5] +
'\nG: ' + studentData[0][6] +
'\nEd: ' + studentData[0][7] +
'\nAVG: ' + Math.round((studentData[0][8] * 100) * 10) / 10 + ' %';
var emailAddress = 'john.doe#example.com';
var subject = 'ALERT - Assessment score below 60% inputted.';
MailApp.sendEmail(emailAddress, subject, message);
}
}
}
}
Check the google referance pages for more info on triggers: https://developers.google.com/apps-script/guides/triggers/events
EDIT
Since you want to use IMPORTRANGE the onEdit trigger will not fire. Try using the function below and use this function with onChange instead of onEdit.
function assessmentOnChange(e) {
var editedSheet = e.source.getActiveSheet();
var scriptProperties = PropertiesService.getScriptProperties();
var propertyKey = 'currentColumn';
var currentColumn = scriptProperties.getProperty(propertyKey);
if (currentColumn === null) {
currentColumn = 14; //Fisrt Empty column is N
}
var table = editedSheet.getRange(3, Number(currentColumn), editedSheet.getLastRow(), editedSheet.getLastColumn()).getValues(),
rowNumber = 3,
valuesToProcess = [];
table.forEach(function(row) {
var column = 0;
row.forEach(function(cell) {
if (typeof cell === 'number') {
valuesToProcess.push({
value: cell,
row: rowNumber,
column: Number(currentColumn) + column
})
}
column++;
});
rowNumber++;
});
var maxColumn = Number(currentColumn);
for (var i in valuesToProcess) {
assessmentOnEdit({
source: e.source,
range: {
getRow: function() {
return valuesToProcess[i].row
},
getValue: function() {
return valuesToProcess[i].value
},
getColumn: function() {
return valuesToProcess[i].column
}
}
});
if (valuesToProcess[i].column > maxColumn) {
maxColumn = valuesToProcess[i].column;
}
}
scriptProperties.setProperty(propertyKey, maxColumn + 1);
}
I apologize in advance if I'm vague or my code is difficult to understand, I'm still learning this stuff. I'm trying to display information that is stored within an array. I want to display this information when a button is clicked and when it is clicked again, the next index in the array displays its information..
I need help setting up a function that advances to the next index of the array. Thanks!
(function(){
var students =[ //array of information
{name:'john',
address:{
address:'821 Imaginary St',
city:'Chicago',
state:'Il'},
gpa:[4.0,3.5,3.8]},
{name:'jim',
address:{
address:'127 fake Rd',
city:'Orlando',
state:'Fl'},
gpa:[2.5,3.3,3.6]}];
var redBut = document.querySelector('.buttonred');
redBut.onclick = getInfo;
var count = 0;
function getInfo(){
var stn = students[0];
if(count<3){
count++;
document.getElementById('name').innerHTML = 'Name: ' + stn.name; //this is what is to be displayed when the button is clicked
document.getElementById('address').innerHTML = 'Address: ' + stn.address.address + " " + stn.address.city + ", " + stn.address.state;
document.getElementById('gpa').innerHTML = 'GPA: ' + stn.gpa[0] +", " + stn.gpa[1] + ", " + stn.gpa[2];
document.getElementById('date').innerHTML = 'Date: ' + d.toLocaleDateString();
document.getElementById('gpaavg').innerHTML = 'Average GPA: ' + gpas;
}
}
I think you want: var stn = students[count];
And not: var stn = students[0];
(DOH!)