Check cell value before post data to server - validation

I'm having a problem checking cell value after an inline edit and before save data. Part of my ColModel is (without any unnesessary code):
{name:'event_start_date',index:'event_start_date',width:75,align:'center',editable:true,edittype:'text',editoptions:{size:'10',maxlength:'10',
dataInit:function(el){
$(el).mask('9999-99-99');
$(el).datepicker({dateFormat:'yy-mm-dd',
beforeShow: function(input, instance){instance.dpDiv.css({marginTop: '1px'});}})
}}
},
{name:'event_start_time',index:'event_start_time',width:70,align:'center',editable:true,edittype:'text',editoptions:{size:'8',maxlength:'8',
dataInit:function(el){$(el).mask('99:99:99');}}
},
{name:'event_end_date',index:'event_end_date',width:75,align:'center',editable:true,edittype:'text',editoptions:{size:'10',maxlength:'10',
dataInit:function(el){
$(el).mask('9999-99-99');
$(el).datepicker({dateFormat:'yy-mm-dd',
beforeShow: function(input, instance){instance.dpDiv.css({marginTop: '1 px'});}})
}}
},
{name:'event_end_time',index:'event_end_time',width:70,align:'center',editable:true,edittype:'text',editoptions:{size:'8',maxlength:'8',
dataInit:function(el){$(el).mask('99:99:99');}}
},
{name:'event_dur_calc',index:'event_dur_calc',width:90,align:'center',editable:false,edittype:'text',sorttype:'date',editoptions:{size:'10',maxlength:'10'}
}
I'm using double click to get inline edit mode. After user make some changes into date/time fields, new value calculated for cell "event_dur_calc":
$('#'+rowId+'_event_start_date').focusout(function(){recalc_dur(rowId);});
$('#'+rowId+'_event_start_time').focusout(function(){recalc_dur(rowId);});
$('#'+rowId+'_event_end_date').focusout(function(){recalc_dur(rowId);});
$('#'+rowId+'_event_endt_time').focusout(function(){recalc_dur(rowId);});
Functions fo calculating new time:
function mktime(){
var i = 0, d = new Date(), argv = arguments, argc = argv.length;
var dateManip = {
0: function(tt){ return d.setHours(tt); },
1: function(tt){ return d.setMinutes(tt); },
2: function(tt){ return d.setSeconds(tt); },
3: function(tt){ return d.setMonth(parseInt(tt)-1); },
4: function(tt){ return d.setDate(tt); },
5: function(tt){ return d.setYear(tt); }
};
for( i = 0; i < argc; i++ ){
if(argv[i] && isNaN(argv[i])){
return false;
} else if(argv[i]){
if(!dateManip[i](argv[i])){
return false;
}
}
}
return Math.floor(d.getTime()/1000);
};
function recalc_dur(rowId){
var event_start_date_txt = $('#'+rowId+'_event_start_date').val();
var event_start_time_txt = $('#'+rowId+'_event_start_time').val();
var event_end_date_txt = $('#'+rowId+'_event_end_date').val();
var event_end_time_txt = $('#'+rowId+'_event_end_time').val();
if (event_end_date_txt=='0000-00-00'){
$('#'+rowId+'_event_dur_calc').val('0000:00:00');
}else{
var start_d_pices = event_start_date_txt.split('-');
var start_t_pices = event_start_time_txt.split(':');
var end_d_pices = event_end_date_txt.split('-');
var end_t_pices = event_end_time_txt.split(':');
var start_time = mktime(start_t_pices[0], start_t_pices[1], start_t_pices[2], start_d_pices[1], start_d_pices[2], start_d_pices[0]);
var end_time = mktime(end_t_pices[0], end_t_pices[1], end_t_pices[2], end_d_pices[1], end_d_pices[2], end_d_pices[0]);
var delta = end_time-start_time;
var secs = delta % 60;
delta = (delta - secs) / 60;
if (secs.toString().length==1) var new_dur = ':0'+secs; else var new_dur = ':'+secs;
var mins = delta % 60;
delta = (delta - mins) / 60;
if (mins.toString().length==1) new_dur = ':0'+mins+new_dur; else new_dur = ':'+mins+new_dur;
var hours = delta;
if (hours.toString().length==1){new_dur = '000'+hours+new_dur;
}else if (hours.toString().length==2){new_dur = '00'+hours+new_dur;
}else if (hours.toString().length==3){new_dur = '0'+hours+new_dur;
}else new_dur = hours+new_dur;
$('tr[id=\"'+rowId+'\"] > td[aria-describedby=\"dfr_event_dur_calc\"]').html(new_dur);
}
}
If the new calculating time will be below zero (when start date/time is greater than end date/time), modal window with alert message is appers and data can't be saved and posted on server.
Is there any event in inline edit mode that fires before posting data, that I can use to check new calculating value to prevent incorrect data will be saved?

The current code of jqGrid on github contains beforeSaveRow callback function which will be called before saving. The changes was made after publishing of jqGrid 4.5.2, but the feature will be exist in the next version (>4.5.2) of jqGrid. So if you don't want use developer sources from github you can use the standard validation way: usage editrules with custom: true and custom_func which do the validation.

Related

Copy/Original printing in Netsuite

I am trying to print original first time and copy after the first time printing. I have created a custom field that stored the timestamp of first time printing. So, the template will check first time the field is empty so "original" is printed and the timestamp will stored to the field. Then, when the template is printed after the first time it will check the field, find that there is a content (The timestamp) so it will print copy on the printed template. everything is work fine, buttt when trying to access the advance template of the applied transaction (like: Bill or any) it show an error like below the code!!! What is the issue?
/**
*#NApiVersion 2.x
*#NScriptType UserEventScript
*/
define(['N/render','N/record'], function(render,record) {
function beforeLoad(context) {
var UserEventType = context.UserEventType;
var contextType = context.type;
var newRecord = context.newRecord;
var newRecordID= context.newRecord.id;
var currentDate = sysDate(); // returns the date
var currentTime = timestamp(); // returns the time stamp in HH:MM:SS
var currentDateAndTime = currentDate + ' ' + currentTime;
if (contextType == UserEventType.PRINT) {
var fieldId = 'custbody_first_print' // fieldId of your custom field / checkbox (or use a datetimestamp)
var isPrinted = newRecord.getValue({ fieldId: fieldId })
if (!isPrinted) {
var myRecord = record.load({id: newRecordID , type: newRecord.type}); // in the beforeLoad, editing the newRecord is not allowed, so you need to load the record first, edit and save.
myRecord.setValue({ fieldId: fieldId, value: currentDateAndTime })
myRecord.save();
}
}
}
function sysDate() {
var date = new Date();
var tdate = date.getDate();
var month = date.getMonth() + 1; // jan = 0
var year = date.getFullYear();
return currentDate = month + '/' + tdate + '/' + year;
}
function timestamp() {
var str = "";
var currentTime = new Date();
var hours = currentTime.getHours();
var minutes = currentTime.getMinutes();
var seconds = currentTime.getSeconds();
var meridian = "";
if (hours > 12) {
meridian += "pm";
} else {
meridian += "am";
}
if (hours > 12) {
hours = hours - 12;
}
if (minutes < 10) {
minutes = "0" + minutes;
}
if (seconds < 10) {
seconds = "0" + seconds;
}
str += hours + ":" + minutes + ":" + seconds + " ";
return str + meridian;
}
return {
beforeLoad: beforeLoad,
};
});
The Error: Error during loading transaction for advanced printing Caused by: com.netsuite.suitescript.exception.NLServerSideScriptException: {"type":"error.SuiteScriptError","name":"SSS_MISSING_REQD_ARGUMENT","message":"load: Missing a required argument: id","stack":["createError(N/error)","beforeLoad(/SuiteScripts/Copy_Original.js:23)","createError(N/error)"],"cause":{"name":"SSS_MISSING_REQD_ARGUMENT","message":"load: Missing a required argument: id"},"id":"","notifyOff":false,"userFacing":true}
At first glance the base problem is that you are not passing a record ID to the record.load method.
A breakdown of the error:
What is the Error: "SSS_MISSING_REQD_ARGUMENT" (you are missing an argument that it needs in order to execute)
Where: "beforeLoad(/SuiteScripts/Copy_Original.js:23)" (the beforeLoad event in your suitescript Copy_Original, on line 23.)
What argument are you missing: "load: Missing a required argument: id" (after the : it tells you id)
I would change the names of your variables so they are different from the netsuite names. particularly newRecord, this will eliminate confusion and the possibility of you referencing a netsuite enum when you are trying to use your variable. (I believe that is what is happening here as I am able to get this to work on my record when using the following.)
function beforeLoad(context) {
var UserEventType = context.UserEventType;
var contextType = context.type;
var recObj= context.newRecord;
var recId= recObj.id;
var currentDate = sysDate(); // returns the date
var currentTime = timestamp(); // returns the time stamp in HH:MM:SS
var currentDateAndTime = currentDate + ' ' + currentTime;
if (contextType == UserEventType.PRINT) {
var fieldId = 'custbody_first_print' // fieldId of your custom field / checkbox (or use a datetimestamp)
var isPrinted = recObj.getValue({ fieldId: fieldId })
if (!isPrinted) {
var myRecord = record.load({id: recId, type: recObj.type}); // in the beforeLoad, editing the newRecord is not allowed, so you need to load the record first, edit and save.
myRecord.setValue({ fieldId: fieldId, value: currentDateAndTime })
myRecord.save();
}
It is worked now, check the sol. :
I should add && recId --->(context.newRecord.id)
if (contextType == UserEventType.PRINT && recId) {
var fieldId = 'custbody_first_print' // fieldId of your custom field / checkbox (or use a datetimestamp)
var isPrinted = recObj.getValue({ fieldId: fieldId })
if (!isPrinted) {
var myRecord = record.load({id: recId, type: recObj.type}); // in the beforeLoad, editing the newRecord is not allowed, so you need to load the record first, edit and save.
myRecord.setValue({ fieldId: fieldId, value: currentDateAndTime })
myRecord.save();

Filter & Delete rows of data based off of column value fast! (Google Sheets)

Is there a way to filter the data in column Q off my google sheet faster then reading line one by one. There is daily about 400+ lines it needs to scan through and I need to delete every row of data if the data in column Q is less than 1 right now watching it, it takes about 10+ minutes.
function UpdateLog() {
var returnSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('CancelRawData');
var rowCount = returnSheet.getLastRow();
for (i = rowCount; i > 0; i--) {
var rrCell = 'Q' + i;
var cell = returnSheet.getRange(rrCell).getValue();
if (cell < 1 ){
returnSheet.deleteRow(i);
}
}
{
SpreadsheetApp.getUi().alert("🎉 Congratulations, your data has been updated", SpreadsheetApp.getUi().ButtonSet.OK);
}
}
Try it this way:
function UpdateLog() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const vs = sh.getDataRange().getValues();
let d = 0;
vs.forEach((r, i) => {
if (!isNaN(r[16]) && r[16] < 1){
sh.deleteRow(i + 1 - d++);
}
});
}
This is a bit quicker
function UpdateLog() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const vs = sh.getDataRange().getValues().filter(r => !isNaN(r[16]) && r[16] < 1);
sh.clearContents();
sh.getRange(1,1,vs.length,vs[0].length).setValues(vs);
}

Is there a way I can make my script more efficient?

I have created the below script to add a timestamp when a cell equals a specific value. This script repeats for each row but it is not efficient. How can I make this simpler and quicker? Below is an extract of the script, it repeats for each row
function MyFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet = ss.getSheetByName('Overview')
var cell = sheet.getRange('H3').getValue()
var sent = sheet.getRange('p3').getValue()
if (cell == "Full" && sent == "") {
sheet.getRange('p3').setValue(new Date())
}
else if (cell == "Open" && sent == "") {
sheet.getRange('p3').setValue("")
}
}
Try this.
function MyFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Overview');
// get range H3:P were H=8, P=16, 9 columns total or index 0 to 8
var data = sheet.getRange(3,8,sheet.getLastRow()-2,9).getValues(); // get range H3:P
var i = 0;
var row = null;
for( i=0; i<data.length; i++ ) {
row = data[i];
if( ( row[0] === "Full" ) && ( row[8] === "" ) ) {
row[8] = new Date();
}
// Your else doesn't do anything if blank set blank ??
row.splice(0,8); // extract only column P
}
sheet.getRange(3,16,data.length,1).setValues(data);
}
Try using setvalues() method of class range
function MyFunction() {
const ss = SpreadsheetApp.getActive()
const sh = ss.getSheetByName('Overview');
const rg = sh.getDataRange();
const vs = rg.getValues();
let vo = vs.map(r => {
if( r[7] == "Full") r[15]=new Date()
if( r[7] == "Open") r[15]='';
return [r[15]];
});
sh.getRange(2,16,vo.length,1).setValues(vo);
}

Reduce script run time in Google Apps Script?

I created a script that tracks attendance for distance learning. After a while it times out so I think I am having issues with too many calls to the Google Classroom API, however I don't see a way that I can change it to take those calls out of a loop.
The script takes all the Google Classroom classes that my apps script account is a co-teacher on and using timed triggers creates a daily attendance assignment with one question that says 'here'. Students are then supposed to answer the question and then another trigger at night runs the function to 'grade' each assignment and populate my spreadsheet so school secretaries can view it in the morning and record the previous days attendance.
The part that seems to have the bottleneck is my getStudentResponses() function. I tried to reduce time by filtering out students that didn't submit the assignment, but it wasn't enough. Does anyone see any way that I can make this faster? I was reading up on using the Cache Service, but I couldn't figure out how to get that to work. Any help would be appreciated.
var ss = SpreadsheetApp.getActive();
var date = new Date();
/*
creates a button to programmatically create all necessary timed triggers for easy deployment
*/
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Attendance')
.addItem('Create Triggers', 'createTriggers')
.addToUi();
}
/*
auto accepts any co-teacher invites
*/
function acceptInvite() {
try{
var optionalArgs = {
userId: "me"
};
var invites = Classroom.Invitations.list(optionalArgs);
for(var i = 0; i < invites.invitations.length; i++) {
Classroom.Invitations.accept(invites.invitations[i].id);
}
}
catch(e){}
}
/*
populates a spreadsheet with all the classes that the script Google account is a co-teacher of
the sheet has two columns one with the course name and two with the course id
*/
function listCourses() {
var optionalArgs = {courseStates: "ACTIVE"};
var response = Classroom.Courses.list(optionalArgs);
var courses = response.courses;
var classSheet;
try{
classSheet = ss.insertSheet("Classes", 0);
ss.insertSheet("Assignments", 1);
}
catch(e) {
classSheet = ss.getSheetByName("Classes");
}
classSheet.clear();
if (courses && courses.length > 0) {
for (i = 0; i < courses.length; i++) {
var course = courses[i];
classSheet.appendRow([course.name, course.id]);
}
}
}
/*
reads the sheet to get all the classes and creates a new array with all the class IDs
*/
function getCourses() {
var classSheet = ss.getSheetByName("Classes");
var classList = new Array();
var range = classSheet.getDataRange();
var values = range.getValues();
for(var i in values) {
var row = values[i];
var courseId = row[1]+"";
classList.push(Classroom.Courses.get(courseId));
}
createTopics(classList);
}
/*
called immediatly after getCourses, creates topics in each class that will contain the daily attendance assignment
*/
function createTopics(classList) {
for(i = 0; i < classList.length; i++) {
var topic;
var resource = {name: "Daily Online Attendance"};
try {
topic = Classroom.Courses.Topics.create(resource, classList[i].id);
createAssignment(topic,classList[i]);
}
catch(e) {
if(e == "GoogleJsonResponseException: API call to classroom.courses.topics.create failed with error: Requested entity already exists") {
var topics = Classroom.Courses.Topics.list(classList[i].id);
for(j = 0; j < topics.topic.length; j++) {
if(topics.topic[j].name == "Daily Online Attendance") {
createAssignment(topics.topic[j], classList[i]);
}
}
}
}
}
}
/*
creates an assignment in each class, under each topic
each assignment only has one choice that says "here" and is going to be 'graded' each night to track attendance
*/
function createAssignment(topic,course) {
var resource = {
title: "Attendance for "+(date.getMonth()+1)+"/"+date.getDate()+"/2020",
description: "Please fill this assignment out each day for attendance",
topicId: topic.topicId,
state: "PUBLISHED",
workType: "MULTIPLE_CHOICE_QUESTION",
multipleChoiceQuestion: {
"choices": [
"Here"
]
}
};
try {
var assignment = Classroom.Courses.CourseWork.create(resource, course.id);
var sheet = ss.getSheetByName("Assignments");
sheet.appendRow([course.id,assignment.id]);
}
catch(e){}
}
/*
creates a new sheet for each day and logs each assignement
*/
function getStudentResponses() {
var assignmentSheet = ss.getSheetByName("Assignments");
var sheet2;
var response;
assignmentSheet.sort(1, true);
try{
sheet2 = ss.insertSheet("Attendance for "+(date.getMonth()+1)+"/"+date.getDate()+"/2020",(ss.getSheets().length-(ss.getSheets().length-2)));
sheet2.appendRow(["Student Last Name","Student First Name","Grade","Class Name","Assignment Answer"]);
}
catch(e) {
sheet2 = ss.getSheetByName("Attendance for "+(date.getMonth()+1)+"/"+date.getDate()+"/2020");
}
sheet2.setFrozenRows(1);
var range = assignmentSheet.getDataRange();
var values = range.getValues();
for(var i in values) {
var row = values[i];
var courseId = row[0]+"";
var courseWorkId = row[1]+"";
try {
response = Classroom.Courses.CourseWork.StudentSubmissions.list(courseId, courseWorkId);
for(var j in response.studentSubmissions) {
if(response.studentSubmissions[j].state == "TURNED_IN") {
try {
var grade;
var email = Classroom.UserProfiles.get(response.studentSubmissions[j].userId).emailAddress;
sheet2.appendRow([Classroom.UserProfiles.get(response.studentSubmissions[j].userId).name.familyName,Classroom.UserProfiles.get(response.studentSubmissions[j].userId).name.givenName,grade,Classroom.Courses.get(courseId).name,response.studentSubmissions[j].multipleChoiceSubmission.answer]);
}
catch (e) {}
}
}
}
catch(e) {}
}
}
/*
deletes all assignemnts that were created
*/
function deleteAssignments() {
var assignmentSheet = ss.getSheetByName("Assignments");
assignmentSheet.sort(1, true);
var range = assignmentSheet.getDataRange();
var values = range.getValues();
for(var i in values) {
var row = values[i];
var courseId = row[0]+"";
var courseWorkId = row[1]+"";
try {
Classroom.Courses.CourseWork.remove(courseId, courseWorkId);
}
catch(e) {}
assignmentSheet.clear();
}
}
function createTriggers() {
ScriptApp.newTrigger('getCourses')
.timeBased()
.everyDays(1)
.atHour(6)
.create();
ScriptApp.newTrigger('getStudentResponses')
.timeBased()
.everyDays(1)
.atHour(22)
.create();
ScriptApp.newTrigger('deleteAssignments')
.timeBased()
.everyDays(1)
.atHour(23)
.create();
ScriptApp.newTrigger('listCourses')
.timeBased()
.everyDays(1)
.atHour(21)
.create();
ScriptApp.newTrigger('acceptInvite')
.timeBased()
.everyDays(1)
.atHour(20)
.create();
}
appendRow is slow, you should avoid to used it inside a for loop. Instead build an array, then pass the values using a single setValues call.
Resources
Best Practices | Apps Script
Related
Google Script Performance Slow Down
Increase my script performance Google Sheets Script
Very slow execution of for...in loop

How to pass a input value into a function using GAS

I will try and keep this brief. I am attempting to make a google web app in google spreadsheet that will allow me to enter a values for min and max.
I have been able to create the GUI and add it to the panel. But I can't seem to pass the integer being entered into another function. I've tried everything, I'm relatively new to creating Google Script so I'm sorry if this comes across as a bit of a noobish problem.
Here is all the code so far :
function onOpen() {
var ss = SpreadsheetApp.getActive();
var menuEntries = [];
menuEntries.push({name: "Open Dialog", functionName: "showDialog"});
ss.addMenu("Min/Max", menuEntries);
}
//creating a panel to add the min and max of low to high for scoring
function showDialog() {
max = 10;
var app = UiApp.createApplication();
app.setTitle("My Applicaition");
var panel = app.createVerticalPanel();
var textBox = app.createTextBox();
var label = app.createLabel("Set the min value for 'Low'");
//had to create a hidden element with id="min" for a global value that can be updated
var min = app.createHidden().setValue('0').setName('min').setId('min');
textBox.setName('myTextBox').setId('myTextBox');
var button = app.createButton('Submit');
panel.add(label);
panel.add(textBox);
panel.add(min);
panel.add(button);
//click handler for setting the value of min to the new value
var clickHandler = app.createServerClickHandler("responedToSubmit");
button.addClickHandler(clickHandler);
clickHandler.addCallbackElement(panel);
app.add(panel);
var doc = SpreadsheetApp.getActive();
doc.show(app);
}
function responedToSubmit(e) {
var app = UiApp.getActiveApplication();
var textBoxValue = e.parameter.myTextBox;
Logger.log(e.parameter.min);
if (typeof textBoxValue != "number") {
var num = parseInt(textBoxValue);
app.getElementById('min').setValue(num);
Logger.log("textBoxValue is = "+textBoxValue+"\n min value is = "+e.parameter.min);
} else {
throw "value needs to be set as number";
}
return app.close();
}
This is where I believe things aren't going according to plan :
function responedToSubmit(e) {
var app = UiApp.getActiveApplication();
var textBoxValue = e.parameter.myTextBox;
Logger.log(e.parameter.min);
if (typeof textBoxValue != "number") {
var num = parseInt(textBoxValue);
app.getElementById('min').setValue(num);
Logger.log("textBoxValue is = "+textBoxValue+"\n min value is = "+e.parameter.min);
} else {
throw "value needs to be set as number";
}
return app.close();
}
I find that each time I test the .setValue() will not update the value of 'min' and I cannot see why. Can you please help?
You need to add textBox element to callBack elements list of your clickHandler.
Try this:
//click handler for setting the value of min to the new value
var clickHandler = app.createServerClickHandler("responedToSubmit");
clickHandler.addCallbackElement(panel);
clickHandler.addCallbackElement(textBox);
button.addClickHandler(clickHandler);
app.add(panel);

Resources