Create custom msgBox button in Google spreadsheet - google-apps-script

I have code that searches a Google spreadsheet for any duplicate entries. When a duplicate is found, a message box pops up to tell the user. It asks the user whether he/she wants to delete the duplicate with YES and NO buttons.
That all works fine. However, ideally, instead of using YES and NO buttons, I want to have three buttons: "Delete Original," "Delete Duplicate," and "Do Not Delete (or CANCEL)," so the user can choose which one to delete.
Is there any way to create custom buttons within a message box? Or, at least, change the names of the stock YES/NO buttons, and have a CANCEL button?
EDIT:
Here is my HTML code to pop up a dialog box:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
function onSuccess(result)
{
var resultInfo = document.getElementById("myPara");
resultInfo.innerHTML = result;
}
google.script.run.withSuccessHandler(onSuccess(result)).passResultToHTML();
</script>
</head>
<body>
<p id = "myPara">This is the default text.</p>
<div id = "myDiv"></div>
<button onclick=google.script.run.deleteOriginalTitle()>Delete Original</button>
<button onclick=google.script.run.deleteDuplicateTitle()>Delete Duplicate</button>
</body>
</html>
EDIT 2:
Code in the .gs:
function findDuplicateTitles()
{
var startRow = Browser.inputBox("At which row would you like to start the search?\\n\\n");
var output = HtmlService.createHtmlOutputFromFile("scriptTestingHTML").setSandboxMode(HtmlService.SandboxMode.IFRAME);
for (var x = (startRow - 1); x < titleColumnArray.length; x++)
{
currentTitle = titleColumnArray[x][0];
var y = x + 1;
for (y; y < titleColumnArray.length; y++)
{
if (titleColumnArray[y][0] == currentTitle)
{
currentTitleValues = sheet.getRange(x + 1, 1, 1, 8).getDisplayValues()
duplicateFound = true;
duplicateCount++;
duplicateRowNum = y + 1;
duplicateTitleValues = sheet.getRange(duplicateRowNum, 1, 1, 8).getDisplayValues();
//resultString is a global String
resultString = "I found a duplicate entry \\n\\n" +
"Original Title on Row: " + (x + 1) + "\\n\\n" +
currentTitleValues[0][0] + " | " +
currentTitleValues[0][1] + " | " +
currentTitleValues[0][2] + " | " +
currentTitleValues[0][3] + " | " +
currentTitleValues[0][4] + " | " +
currentTitleValues[0][5] + " | " +
currentTitleValues[0][6] + " | " +
currentTitleValues[0][7] + " | " +
"\\n\\n" +
"Duplicate Title on Row: " + duplicateRowNum +
"\\n\\n" +
duplicateTitleValues[0][0] + " | " +
duplicateTitleValues[0][1] + " | " +
duplicateTitleValues[0][2] + " | " +
duplicateTitleValues[0][3] + " | " +
duplicateTitleValues[0][4] + " | " +
duplicateTitleValues[0][5] + " | " +
duplicateTitleValues[0][6] + " | " +
duplicateTitleValues[0][7] + " | " +
"\\n\\nDelete this duplicate? Or delete the original?";
Logger.log(resultString);
SpreadsheetApp.getUi().showModalDialog(output, "Duplicate Entry Found");
Utilities.sleep(5000);
}
}
}
After resultString is set and the dialog opens, the HTML calls this funtion:
function passResultToHTML()
{
return resultString;
}

Sadly, this is currently not possible in a standalone script. However, what you could do is make this script container-bound (AKA an add-on). Then instead of using Browser, you could use SpreadsheetApp.getUi().showModalDialog()
https://developers.google.com/apps-script/reference/base/ui#showmodaldialoguserinterface-title
This will allow you to pop up a custom HTML file as a dialog box, and you can set custom responses to your buttons with Javascript.

Related

spreadsheets.values.batchUpdate() completes successfully, but the destination sheet is still empty

The script doesn't throw any errors, but rarely completely works - i.e. complete successfully with all of the expected data in the destination tab. The results breakdown is generally:
no results in the destination sheet - this happens ~50-75% of the time
all of the results in the destination sheet, except in cell A1 - ~25% of the time
100% completely works - ~15-25% of the time
code snippet of the batchupdate() call
var data = [
{
range: (ss.getSheetName() + "!A1:AQ" + valueArray.length)
,values: valueArray
}
];
const resource = {
valueInputOption: "RAW"
,data: data
};
Logger.log("request = " + JSON.stringify(resource)
+ "\n" + "valueArray = " + valueArray.length
);
Logger.log(" Sheets.Spreadsheets.Values.batchUpdate(params, batchUpdateValuesRequestBody) ");
var response = Sheets.Spreadsheets.Values.batchUpdate(resource, spreadsheetId);
Logger.log("response = " + response.toString());
and the response
response = {
"totalUpdatedRows": 37776,
"responses": [{
"updatedCells": 1482389,
"updatedRange": "BatchUpdateDestination!A1:AP37776",
"updatedColumns": 42,
"spreadsheetId": "adahsdassadasdsadaasdasdasdasdasdasdasdasdas",
"updatedRows": 37776
}
],
"spreadsheetId": "adahsdassadasdsadaasdasdasdasdasdasdasdasdas",
"totalUpdatedCells": 1482389,
"totalUpdatedSheets": 1,
"totalUpdatedColumns": 42
}
Its obviously a very large dataset, but I've pruned the destination spreadsheet to ensure there is ample room for the data, and from earlier testing, I believe that a specific size error would be returned if that was the blocker.
How can I troubleshoot, or better yet, prevent these incomplete executions? is there any way to inspect the batch jobs that these requests initiate?
Answering my own question...
After toiling with this a little more, I couldn't figure out any way to troublshooting or inspect the odd, seemingly successfully batchUpdate() jobs. Thus, I resorted to batching the batchUpdate() calls into batches of 15000. This seems to work consistently, though maybe a bit slower:
// This is the very large 2D array that is populated elsewhere
var valueArray = [];
var maxRows = valueArray.length;
var maxCols = valueArray[0].length;
var batchSize = 15000;
var lastBatchSize = 1;
for (var currentRowCount = 1; currentRowCount <= maxRows; ++currentRowCount) {
if( currentRowCount % batchSize == 0
|| currentRowCount == maxRows
)
{
Logger.log("get new valuesToSet");
valuesToSet = valueArray.slice(lastBatchSize - 1, currentRowCount -1);
var data = [
{
range: (ss.getSheetName() + "!A" + lastBatchSize + ":AQ" + (lastBatchSize + valuesToSet.length))
,values: valuesToSet
}
];
const resource = {
valueInputOption: "RAW"
,data: data
};
Logger.log("request = " + JSON.stringify(resource).slice(1, 100)
+ "\n" + "valuesToSet.length = " + valuesToSet.length
);
try {
var checkValues = null;
var continueToNextBatch = false;
for (var i = 1; i <= 3; ++i) {
Logger.log("try # = " + i
+ "\n" + " continueToNextBatch = " + continueToNextBatch
+ "\n" + " make the batchUpdate() request, then sleep for 5 seconds, then check if there are values in the target range."
+ "\n" + " if no values, then wait 5 seconds, check again."
+ "\n" + " if still not values after 3 tries, then resubmit the batchUpdate() requestion and recheck values"
+ "\n" + "range to check = " + "A" + lastBatchSize + ":AQ" + lastBatchSize
);
Logger.log(" Sheets.Spreadsheets.Values.batchUpdate(params, batchUpdateValuesRequestBody) ");
var response = Sheets.Spreadsheets.Values.batchUpdate(resource, spreadsheetId);
Logger.log("response = " + response.toString());
/// loop and check for data in newly written range
for (var checks = 1; checks <= 3; ++checks) {
Utilities.sleep(5000);
var checkValues = ss.getRange(("A" + lastBatchSize + ":AQ" + lastBatchSize)).getValues();
Logger.log("new cell populated - checks # = " + checks
+ "\n" + "range to check = " + "A" + lastBatchSize + ":AQ" + lastBatchSize
+ "\n" + "checkValues.length = " + checkValues.length
+ "\n" + "checkValues = " + checkValues
);
if(checkValues.length > 1)
{
Logger.log("checkValues.length > 1, so continue to next batch"
+ "\n" + "range to check = " + "A" + lastBatchSize + ":AQ" + lastBatchSize
+ "\n" + "checkValues.length = " + checkValues.length
+ "\n" + "checkValues = " + checkValues
);
continueToNextBatch = true;
continue;
}
else
{
Logger.log("checkValues.length is still not > 1, so try the request again"
+ "\n" + "range to check = " + "A" + lastBatchSize + ":AQ" + lastBatchSize
);
}
}
if(continueToNextBatch)
{
continue;
}
}
}
catch (e) {
console.error("range.setValues(valuesToSet) - yielded an error: " + e
+ "\n" + "valuesToSet = " + valuesToSet.length
+ "\n" + "maxRows = " + maxRows
+ "\n" + "maxCols = " + maxCols
+ "\n" + "currentRowCount = " + currentRowCount
+ "\n" + "current range row start (lastBatchSize) = " + lastBatchSize
+ "\n" + "current range row end (j - lastBatchSize) = " + (currentRowCount - lastBatchSize)
);
}
lastBatchSize = currentRowCount;
}
}

how to retrieve text data in fabricjs?

I wanted to retrieve text data from the fabric text.
I tried with .get('text') but it's telling undefined.
This is my HTML code :
function save(obj){
canvasfabric.forEachObject(function(obj){
// var textname = canvasfabric.setActiveObject().setText(event.target.value);
// alert(textnew);
//var text = canvasfabric.getActiveObject();
// alert(text.get('type'));
//canvasfabric.setActiveObject(textnew);
//var text = obj.get('type');
// var text1 = obj.get('text');
alert("frame : " + i + "\n"
+ "x : " + obj.left + "\n"
+ "y : " + obj.top + "\n"
+ "width : " + obj.width + "\n"
+ "height : " + obj.height + "\n"
+ "class : " + obj.get('text'));
});
//alert("Data has been saved Successfully");
}
Anybody here can help me to retrieve objects text data.

How can I display a profile stored in a database after a SQL query in a Google Spreadsheet Sidebar?

Basically here is what I'm doing:
Storing a person profile in a MYSQL Database
Creating a google spreadsheet add-on with a sidebar
giving to the user the possibility to search profiles through the DB in the add-on
giving to the user the possibility to display the datas of the profile in the Sidebar in the add-on
So Imagine you want to search John but the input you've entered in the searchbar is the letter J. The result of this query will be Jack , Johnatan, Jules, Joe and John as they all have a J. All this names will appear as link for you to click on in order to have more datas displayed (age, picture, fullname, description...). Only problem is that the only way I've managed to achieve is by making a SQL query after each keyup in my HTML input then create X numbers (in this case 5) of hidden classes with the infos, that I can show by clicking on a created link.
So this can easily work for 5 results, but imagine there's 100 of them and you only want to see one of them. Sounds crazy to create 100 hidden divs with all the infos to finally show only one.
here is an example code
code.gs
function onOpen(e) {
SpreadsheetApp.getUi().createAddonMenu()
.addItem('Start', 'showSidebar')
.addToUi();
}
function onInstall(e) {
onOpen(e);
}
function showSidebar() {
var ui = HtmlService.createHtmlOutputFromFile('UI_HTML');
SpreadsheetApp.getUi().showSidebar(ui);
}
//My function to search for profiles (returns an array with each lines found)
function searchProfile(entry){
var address = 'sql3.freemysqlhosting.net';
var user = 'user';
var userPwd = 'password';
var db = 'sql12345678910';
var instanceUrl = 'jdbc:mysql://' + address;
var dbUrl = instanceUrl + '/' + db;
var conn = Jdbc.getConnection(dbUrl, user, userPwd);
var stmt = conn.createStatement();
var ret = [];
var res = stmt.executeQuery('SELECT * FROM item WHERE name = "' + entry + '"'
+ ' OR fullname LIKE CONCAT("%", "' + entry + '", "%")'
+ ' OR description LIKE CONCAT("%", "' + entry + '", "%")');
var numCol = res.getMetaData().getColumnCount();
var i = 0;
var j = 0;
while (res.next())
{
j = 0;
if (!ret[i])
ret[i] = [];
while (j < numCol)
{
ret[i][j]= res.getString(j + 1);
j++;
}
i++;
}
return (ret);
}
UI_HTML.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style>
.show {
display: block;
}
.hide {
display: none;
}
</style>
</head>
<body>
<div>
<p>Search Profile</p>
<input type="text" id="searchProfile" onkeyup="searchProfileHTML()" placeholder="Search for ...">
<div id="results" ></div>
</div>
<script>
function searchProfileHTML()
{
var x = document.getElementById("searchProfile").value;
google.script.run
.withSuccessHandler(createLinks)
.searchProfile(x);
function createLinks(result)
{
$('.links').remove();
if (!x)
return (0);
$('#results').append('<ul></ul>');
$('#results ul').addClass('links');
result.forEach(function(el){
//My Link to show/hide my Div
$('.links').append('<li><a id="' + el[0] + '-link" href="#">' + el[0] + '</a><div id="' + el[0] + '" class ="hide" ></div></li>');
//My div with all the infos (hidden by default)
$('#' + el[0]).append(""
+ '<img src="' + el[14] + '" height="100" width="100">'
+ "<div>"
+ "<ul>"
+ "<li>Name: "+ el[0] + "</li>"
+ "<li>Full name: "+ el[1] + "</li>"
+ "<li>Description : "+ el[2] + "</li>"
+ "<li>email: "+ el[3] + "</li>"
+ "</ul>"
+ "</div>");
$('#' + el[0] + '-link').on("click", function(){
$('#' + el[0]).toggleClass("hide show");
})
});
}
}
</script>
Anyone has an idea how I can make this more lite?

How to read Div inner contents at ajax?

I'm trying to update the sql db by a List of variables sent from the Html page.
Some of the Data are correctly sent, while others are not. I put the list in a div which is divided to two parts : "h1" and another "Div". The data at the header are all sent correctly, but the body itself which is at the second div isn't sent at all.
This is the Div which the data is put at:
$('#Classes').append('<div> <h1 class = "flip" wpID="' + subjects[i].Wkp_ID + '" lessonID="' + subjects[i].Ttb_lessonID + '" Date="' + Datecoming + '">' + subjects[i].sbj_Name + " Class:" + subjects[i].Ttb_Class + '</h1><div id ="NewBody" class="panel" contenteditable>' + subjects[i].Wkp_Body + '</div> </div>');
And that's how I read them at the ajax part:
var WeekPlan = [];
$('#Classes div').each(function (index) {
var header = $(this).children('h1');
var WeekBody = $(this).children('div').val();
var wpID = header.attr('wpID');
var lessonID = header.attr('lessonID');
var Wkp_Date = header.attr('Date');
WeekPlan[index] = { "Wkp_ID": wpID, "Wkp_Date": Wkp_Date, "Wkp_Body": WeekBody, "Wkp_lesson": lessonID };
});
The Wkp_ID, Wkp_Date, Wkp_Lesson are right, but the Wkp_Body just returns an empty string.
So do you know why is this happening and how can I truly read the body ? Most probably the problem is with this line:
var WeekBody = $(this).children('div').val();
But how can I access it correctly ?
Thanks a lot.
Here is what worked for me in case anyone needs it:
Creating the div:
$('#Classes').append('<div class="BigDiv"> <h1 class = "flip" wpID="'+ subjects[i].Wkp_ID + '" lessonID="' + subjects[i].Ttb_lessonID + '" Date="' + Datecoming + '">' + subjects[i].sbj_Name + " class:" + subjects[i].Cls_Name + '</h1><div class="panel" contenteditable>' + subjects[i].Wkp_Body + '</div> </div>');

Auto select the check box in rows

I am displaying table rows in the form. Each row has 5 columns, one of the columns is an (editable) textbox field.
ajaxLoading(true);
$.post('<%=request.getContextPath()%>'+"/processServlet", postData,
function(data) {
var ctxPath='<%=request.getContextPath()%>';
currentPosition = data.currentPosition;
var items = $("#itemsTable");
items.empty();
if (data.items.length == 0) {
items.append($('<tr><td colspan=5 style="color:red;">No items<td></tr>'));
}
;
for (var i = 0; i < data.items.length; i++) {
editText = "";
items.append($("<tr " + zebra + "><td><a href=\"javascript: deviceView('" + data.items[i].id + "')\">" + data.items[i].num +
"</a></td><td>" + data.items[i].itemType +
"</td><td><input type = 'checkbox' id = 'CheckBoxRow_' />" + data.items[i] +
"</td><td><input type = 'textbox' id = 'TextBoxRow_' value = '" + data.items[i].itemName +"' "/>" +
"</td><td>" + data.items[i].status +
"</td><td>" + data.items[i].date + "</td>" +
"<td>" + data.items[i].firmware + "<td>" +
"Delete" +
"</tr>"));
;
ajaxLoading(false);
}, "json");
How do I auto select the check box when the data is entered or modified in the textbox and save the data in the database?
bind to textbox onchange event and have it set the checkbox to checked=true once returned with a value (and not already checked).
$('#itemsTable input[type="checkbox"]').change(function(e){
var $cb = $(this);
if ($cb.is(':checked')){
$cb.closest('tr').find('input[type="checkbox"][id^=CheckBoxRow_]').prop('checked','true');
}
});
basically. though you really should not use HTML in an append, and should be building the objects with jQuery.