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.
Related
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;
}
}
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.
Here is my JSON
var gal = [
{
"folder":"nu_images",
"pic":"gd_42.jpg",
"boxclass":"pirobox_gall",
"alt":"Rand Poster 1",
"title":"Rand Poster 1",
"thfolder":"th",
"thumbpic":"th_gd_42.jpg"
},
{
"folder":"nu_images",
"pic":"gd_13.jpg",
"boxclass":"pirobox_gall",
"alt":"Explosive Pixel Design",
"title":"Explosive Pixel Design",
"thfolder":"th",
"thumbpic":"th_gd_13.jpg"
}
];
and here is my for loop
for (i = 0; i < gal.length; i++) {
document.getElementById("gallery").innerHTML = "" + "<img src=\"" + "http:\/\/galnova.com\/" + gal[i].folder + "\/" + "th\/" + gal[i].thumbpic + "\"" + "border=\"0\"" + "alt=\"" + gal[i].alt + "\"" + "title=\"" + gal[i].title + "\"\/>" + ""
};
I am trying to make my JSON show all of the objects in HTML one after the other. I can get it to show the first one or whatever number I put into the array but I don't know how to make it generate a list of them.
Here is a link to my jsfiddle. Any help you can offer would be greatly appreciated.
http://jsfiddle.net/o7cuxyhb/10/
It's being generated here <p id="gallery"></p> just not correctly.
You're overwriting your html with every loop iteration:
document.getElementById("gallery").innerHTML = ...
^---
Perhaps you want something more like
document.getElementById("gallery").innerHTML += ...
^---
which will concatenation the original html contents with your new stuff.
And technically, you shouldn't be doing this in a loop. Changing .innerHTML like that causes the document to be reflowed/re-rendered each time you change .innerHTML, which gets very expensive when you do it in a loop. You should be building your html as a plain string, THEN adding it to the dom.
e.g.
var str = '';
foreach(...) {
str += 'new html here';
}
document.getElementById("gallery").innerHTML += str;
for (i = 0; i < gal.length; i++) {
document.getElementById("gallery").innerHTML += "" + "<img src=\"" + "http:\/\/galnova.com\/" + gal[i].folder + "\/" + "th\/" + gal[i].thumbpic + "\"" + "border=\"0\"" + "alt=\"" + gal[i].alt + "\"" + "title=\"" + gal[i].title + "\"\/>" + "" };
Add a += instead of an = after innerHTML
Try this:
function displayJson(jsonArray){
var container = document.getElementById("gallery");
for (var i=0; i<jsonArray.length; i++){
var newElement = document.createElement("a").innerHTML = jsonToHtml(jsonArray[i])
container.appendChild(newElement);
}
}
function jsonToHtml(jsonObj){
//Define your dom object here
var el = document.createElement("a").innerHTML = '' // you code here
...
return el;
}
displayJson(gal);
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>');
I've been working with highcharts and MVC 3 for two years by now (I never done anything complicated, just load data and make it work stuff), and I worked with two different scenarios:
Chart code written in the directly in the view, loading data through Json
Html helper responsible to plot the chart
The Html helper approach seems to me a more elegant choice ... but then, just to illustrate to you guys, here is how it looks like (just part of it):
public static string DisplayChart(
this HtmlHelper helper,
ChartOptions options,
TimedChartSeries[] data)
{
string[] axisList = data.GroupBy(t => t.Unit).Select(t => t.Key).ToArray();
string result = "";
result += "<script type=\"text/javascript\">\n";
result += "var " + options.ChartName + ";\n";
result += "$(document).ready(function() {\n";
result += options.ChartName + "= new Highcharts.Chart({\n";
result += "chart: {renderTo: '" + options.DivName + "',zoomType: ''},\n";
result += "title: { text: '" + options.Title + "'},\n";
result += "subtitle: {text: '" + options.SubTitle + "'},\n";
result += "xAxis: { type: 'datetime'," +
"\n dateTimeLabelFormats: {month: '%e. %b', year: '%b' },"
+ "labels:{rotation: -45}\n},\n";
string axes = "";
for (int i = 0; i < axisList.Length; i++)
{
var temporaryData = data.First(t => t.Unit == axisList[i]);
if (i != 0)
axes += ", ";
axes += "{labels:{\n " +
"formatter : function(){return this.value + '" + temporaryData.Unit + "';},\n" +
"style:{color:'#" + temporaryData.Color.Name.Remove(0, 2) + "'}},\n" +
"title:{text:'',style:{color:'#" + temporaryData.Color.Name.Remove(0, 2) + "'}},\n" +
"}\n";
}
result += "yAxis: [\n" + axes + "],\n";
string units = "";
for (int i = 0; i < data.Length; i++)
{
if (i != 0)
units += ", ";
units += "'" + data[i].Title + "': '" + data[i].Unit + "'\n";
}
result += "tooltip:{\nshared: true,\n backgroundColor: 'none' ,\nborderColor: 'none'," +
"\nshadow:false\n ,crosshairs: true,\n" +
"formatter: function() {var s = '<table class=\"table-list\"><tr><th>Hora</th><th>'+ test(this.x) +'</th></tr>';" +
"\n$.each(this.points, function(i, point) {" +
"\ns += '<tr><td>'+point.series.name + '</td><td>'+point.y+'</td></tr>'});s+='</table>';" +
"\n$('#tooltip').html(s);}},";
result += "lang: {" +
"months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho'," +
"'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro']," +
"weekdays: ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado']},";
string series = "";
int x = 0;
for (int j = 0; j < axisList.Length; j++)
{
var temporaryData = data.Where(t => t.Unit == axisList[j]).ToArray();
for (int i = 0; i < temporaryData.Length; i++)
{
if (x > 0)
series += ", ";
series += "{name: '" + temporaryData[i].Title + "',\n color: '#" + temporaryData[i].Color.Name.Remove(0, 2) +
"', \ntype: '" + temporaryData[i].Type + "',\nunit:'" + temporaryData.First().Unit + "', \nyAxis:" + j + " , \ndata:[" + FromArrayToString(temporaryData[i].Data) +
"], marker: { enabled: false}}\n";
x++;
}
}
result += "series: [\n" + series + "]\n";
result += "});});";
result += "\nfunction test(i)\n{\nvar j = new Date(i + 2*60*60*1000);\n" +
"return new Date(i + 3*60*60*1000).format('d/m/Y H:i:s.')+j.getMilliseconds();\n}\n</script>";
result += "\n<div id=\"" + options.DivName + "\" style=\"width:" + options.Width + ";height: " + options.Height + "\"></div>" +
"<div id=\"tooltip\"></div>";
return result;
}
It's really simple to call this helper:
#Html.Raw(Html.DisplayChart((ChartOptions)Model.Options,(TimedChartSeries[])Model.Series))
As you guys can see, I have to use the Html.Raw helper in order to make it work ... that is problem nº 1 (and it probably has an easy solution). But the second problem is really great: the chart becomes entirely tied to my domain. If I wanted to plot a, say, bar chart displaying data of the last 3 years in months (each month being represented by a bar), it would be impossible to use this helper.
And it also looks kind of ugly.
So, guys, which option do you think is more elegant, the Json or the Helper approach?
About the use of Html.Raw and the easy solution:
Change your function to
public static HtmlString DisplayChart(this HtmlHelper helper, ...)
{
...
return new HtmlString(result);
}
You then may use #Html.DisplayChart(...) in your razor views.
Also, please make sure that options.DivName, options.Title, options.SubTitle etc. are properly escaped - a title like Everybody's favorite chart will break the output.