I'm working on a simple electron / node.js app, which takes input information locally, do some calculation and present calculation results in a table
Input:
10s of rows of features info
ID | length | depth |
Calculation: takes about seconds for each feature calculation.
Output:
Some amount of row from input, but with a results column
ID | length | depth | Results
I tried to append the results to tables for presenting, the code looks like this
<table id = 'results-table'>
<th>ID</th><th>length</th><th>depth</th><th>Results</th>
</table>
function getCrackTableValues(){
document.body.style.cursor = 'wait';
var table = document.getElementById('input-table'); //get info from input table
for (var r = 1, n = table.rows.length; r < n; r++) { //skip table head tart from second row
const crackID = table.rows[r].cells[0].innerHTML; //input
var a_m = Number(table.rows[r].cells[1].innerHTML); // input
var c_m = Number(table.rows[r].cells[2].innerHTML); //input
var result = foo(a_m, c_m); //foo just for example, takes seconds for calculation
var newRow = document.getElementById('results-table').insertRow(); //output table
newRow.innerHTML = '<td>'+ crackID+
'</td><td>' + `${a_m.toFixed(1)}` +
'</td><td>' + `${c_m.toFixed(1)}` +
'</td><td>' + `${result.toFixed(1)}` + //append results to output table
'</td>';
}
document.body.style.cursor = 'default';
}
The code works ok, but with couple issues.
Q1: I was hoping once a single calculation is done, it will show results on the output table, and move to next entry's calculation. So the user could see current results, while the calculation of the rest rows are going on.
Currently it seems the output table won't show up until all the calculations are done.
Are there any ways I could use to make the results table grows/shows up one by one, as calculation of each row goes on?
Q2: I was trying to disable the cursor while the calculation goes on document.body.style.cursor = 'wait';. and enable the cursor back on after all calculations are done.
But it seems this line executed after calculation, it would just disabled the cursor and flash back on.
Are there any potential issue with my current implementation? My node.js is v12.16.3, on a old 32bit windows 7.
The cause of both issues is the same: the UI won't get chance to update until it manages to escape your very-long-running loop.
You need to change foo() to become an async function. You've not shown any details, so I will assume it is pure CPU calculation and has no file or network access points.
I think I'd first pull out the loop contents into another function. And then I've put the loop termination condition at the top of that new function:
function processOneRow(r){
const table = document.getElementById('input-table');
if(r >= table.rows.length){ //All done
document.body.style.cursor = 'default';
return;
}
const crackID = table.rows[r].cells[0].innerHTML;
const a_m = Number(table.rows[r].cells[1].innerHTML);
const c_m = Number(table.rows[r].cells[2].innerHTML);
const result = foo(a_m, c_m);
const newRow = document.getElementById('results-table').insertRow();
newRow.innerHTML = '<td>'+ ...;
setTimeout(processOneRow, 0, r+1)
}
And then you can start it going by calling it for the first row:
function getCrackTableValues(){
document.body.style.cursor = 'wait';
setTimeout(processOneRow, 0, 1)
}
The use of setTimeout() with a 0ms delay, gives the UI thread time to update each time, before it will call processOneRow() on the next row of the input table.
Aside: Putting the code to restore the cursor inside processOneRow() is a bit of a code smell. If this code was going in a library, I would probably use before()/after() hooks. (You could then also make sure the after() hook gets called if there is an exception thrown.)
The other way to approach this would be to use worker threads and move the foo(a_m, c_m) calculation there. That would then naturally be async. Beyond the extra complexity, the downside of that is if foo() uses data, it needs to be kept with it in that worker thread, which gets complex if the same data is needed in the main thread. But, otherwise, it is a better solution for long running processes.
Related
I have a script that successfully copies data from one Google Spreadsheet to another. What I'm trying to figure out is how to write the data to say Column 5 and not Column 1. When I alter the script to write the data to Column 5, it processes the data but shows a "cell reference out of range" error.
Here is the code that successfully copies data to Column 1.
'function copy_data() {
var source = SpreadsheetApp.openById('1iQm5NDahmUqXXLDAFvVkrNqooHd2-AAEDVRXndEWXbw').getSheetByName('Account-Category Database');
var target = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet25');
var lastdatarow = source.getLastRow();
var criteria = target.getRange('A1').getValue();
var codedata = source.getRange(3,2,lastdatarow,2).getValues();
var codeout = [];
for (var i=2 in codedata) {
if (codedata[i][0] == criteria || codedata[i][0] == "All") {
codeout.push(codedata[i])
}
}
target.getRange(2,1,codedata.length,codedata[0].length).setValues(codedata).sort(2);
}
PROBLEM:
When I change the bottom of the formula to:
target.getRange(2,5,codedata.length,codedata[0].length).setValues(codedata).sort(2);
}
That's when I get the "Cell reference out of range" error. Again, the data still copies, it just stops the next function from running because of the error.
Turns out I'm just an idiot =) The error with the code was trying to ".sort(2)"... That means I was trying to sort the target sheet by Column 2, when I was writing data to columns 5... thus Column 2 was out of range... Changing it to ".sort(5)" corrected the issue.
Hopes this helps others understand their potential future issue.
I have looked everywhere I can think of for anything that can provide an answer to this. First time posting a question here - I can usually find my answers. I have a for loop that is pulling information from a range of data that is formatted in one cell like this: 09/01/2016 - Status changed to active.
The loop is supposed to first see how many values are in that column then go one by one and split the data into a simple array, post it into two columns on a separate sheet, then move onto the next one. The problem is that it stops after the first entry.
var numEntries = dataSheet.getRange(1,i+1,1000).getValues();
var lastEntry = numEntries.filter(String).length;
if (lastEntry == 7) {
// no change data to date
sheet.getRange(18,3).setValue("No changes yet");
} else {
var changeData = dataSheet.getRange(8,i+1,lastEntry-7).getValues();
for (var y = 0; y < changeData.length; y++) {
var changeHistory = changeData[y][y].split(" - ");
sheet.getRange(nextRow+1,2).setValue(changeHistory[0]);
sheet.getRange(nextRow+1,3).setValue(changeHistory[1]);
nextRow++;
Logger.log(nextRow);
Logger.log(changeData.length);
Logger.log(y);
}
}
I know that it is executing properly because it is properly setting the "No changes yet" value when there are no entries. Variable nextRow starts at a value of 17, and the log properly shows changeData.length with the number of entries and y being equal to 0. But then it stops. It doesn't follow the loop that y is still less than changeData.length. Any help is very much appreciated!
[edit] - I also want to point out that it does properly split and populate the first value into the two cells I want it to, so the whole for statement does work, it just doesn't loop. [edit]
[16-09-29 15:37:48:514 CDT] 18.0 [16-09-29 15:37:48:515 CDT]
11.0 [16-09-29 15:37:48:515 CDT] 0.0
changeData is an n*1 Array.
You are increasing y and and you are also trying to get the y-th column from changeData.
After the first iteration this is undefined because there's only one column.
undefined does not have a split method throwing an exception and terminating the script. (You may not see this exception, these kinds of exceptions aren't always shown to the user for some reason)
Try
var changeHistory = changeData[y][0].split(" - ");
instead.
I am working on a survey in qualtrics, and need to be able to set many embedded data fields with different values, depending on a random selection of the experimental condition.
In the Survey Flow, I've entered all the names of the desired embedded data values.
I want this code to execute at the start of the survey, and to go through and set the values of those Embedded data variables.
The reason I use a for loop is because the variables are named things like:
set1a
set1b
set1c
var condition = Math.floor((Math.random() * 3) + 1)
var conditions =[ [8,8,8,5,5,5,5,2,2,2], [8,8,8,7,7,7,7,2,2,2], [10,10,10,5,5,5,5,2,2,2], [10,10,10,7,7,7,7,2,2,2]]
var values = condition[conditions]
var letters ="abcdefghij"
Qualtrics.SurveyEngine.addOnload(function(values)
{
for (var i = 0; i < values.length; i++ ) {
var dataname = "set1" + letters.charAt(i)
Qualtrics.SurveyEngine.setEmbeddedData(dataname,values[i]);
}
});
However, this code doesn't work.
When I try to call the variables using piped text later on in the survey (after a page break) they do not appear to have stored.
You have two issues.
First, you can only set Javascript in Qualtrics if it's inside of the .addOnload function, e.g.:
Qualtrics.SurveyEngine.addOnload(function(values)
{
// Your code
});
Second, your method for assigning - values - throws an error message.
Qualtrics doesn't have an issue saving embedded data in a for loop, so if you fix these two points, you should be good to go.
Working on generating table using datatable.
I have textbox below the table, when I add any value to that textbox then that value should be updated to specific column of 1st row then each row should be increment by 1 for that column(new updated value).
I have code for that, that works fine for me. But it takes too much time for that. I have 159 records in table and when I update column then it takes about to 8-9 seconds, this is very long duration. Till execution complete I am not able to do anything.
JS code:
updateNo: function(dataTableId, noColIndex, numToAdd) {
var dataTable = $(dataTableId).dataTable();
var numberOfRows = dataTable.api().rows().data().length;
var index;
for (index = 0; index < numberOfRows; index++) {
var cell = dataTable.api().cell(index, noColIndex);
var currentNo = cell.data();
var newNo = parseInt(currentNo) + numToAdd;
cell.data(newNo).draw();
}
}
I have tried to calculate method execution time and found that last line of method: cell.data(newNo).draw(); takes more time to execute.
I am not so much proficient in JavaScript or Jquery so I don't know the reason. If someone knows the reason and fix for this issue then please let me know.
I want to decrease execution time for this.
You don't have to redraw every cell, just redraw the entire table once after executing your code
So, I'm trying to create a small table for working out the potential winnings of betting on a rubber duck race when each duck has certain odds.
I sort of have this working but have hit a stumbling block...
When the page loads all of the maths is done correctly based on the default value of £10. What I then want to do is allow people to change the amount of money they would like to bet per duck and the potential winnings for that duck only updates automatically.
This is what I have so far:
function calculate_odds(row) {
var winnings = 0;
// Set winnings to 0
var odds = $(row).find('#theodds').attr('data-duckodds'),
// Find the value of the data attribute for a specific td
betting_amount = $('[name="betting-amount"]').val(),
// Find the value entered into an input field
winnings = (parseFloat(odds) / 1 + 1) * betting_amount;
// Work out the winnings based on the odds given.
// Divide first number by 1 as all odds are something to 1 for now, then +1
// Multiply that number by the bet
// For example Bet 30 on 3/1 odds
// 3 / 1 = 3 + 1 = 4 * 30 = 120
$(row).find('.js-winnings').html(winnings.toFixed(2));
// Show the winnings amount in the final td in each row
}
$(document).ready(function() {
$('.lineup tbody tr').each(function() {
// Run the function calculate_odds() initially based on the default value;
calculate_odds();
$(this).find('[name="betting-amount"]').on('keyup', function() {
// Now loop through each row and change the winnings amount if the betting amount is changed
calculate_odds($(this).closest('tr'));
}).trigger('keyup');
})
});
From what I understand (my jQuery is not great), within this line: $(this).find('[name="betting-amount"]').on('keyup', function() { all I should need to do is select the specific row I want to update. Which should be simple right?
Instead what this does is takes the updated value from the first row and then applies as that as you change the later rows.
Can anyone point our where I'm going wrong? You can see the calculator here: http://www.kb-client-area.co.uk/ducks/races/race-1/
Thanks in advance :)
The specific problem you're encountering is where you're setting betting_amount:
betting_amount = $('[name="betting-amount"]').val()
You're looking globally in the document, and so finding the first instance.
Switching it to this makes it work:
betting_amount = $(row).find('[name="betting-amount"]').val()
As an aside: it would be better to use a class instead of an ID for #theodds, as IDs are supposed to be unique per document :)
I think perhaps your 'this' isn't referring to what you think it is in this line: calculate_odds($(this).closest('tr'));
Could you try something along the lines of:
$(this).find('[name="betting-amount"]').on('keyup', function(e) {
// Now loop through each row and change the winnings amount if the betting amount is changed
calculate_odds($(e.target).closest('tr'));
}).trigger('keyup');
})
As I said earlier, element IDs must be unique in a given document. You have the id theodds repeated as many times as the number of rows in your table which makes your HTML invalid! Remove those IDs and you could just work around the data-* that you already have.
function calculate_odds(row) {
row = $(row);
var winnings = 0,
//find the data-* from within the context of the keyup - the row
odds = row.find('[data-duckodds]').attr('data-duckodds'),
//find the entered amount from within the context of the keyup - the row
betting_amount = row.find('[name="betting-amount"]').val(),
//Your math
winnings = (parseFloat(odds) / 1 + 1) * betting_amount;
row.find('.js-winnings').html(winnings.toFixed(2));
}
$(document).ready(function() {
//You don't need an each loop here as the keyup will be triggered on every amount box on load as well.
$('.lineup').on("keyup", "[name='betting-amount']", function() {
calculate_odds($(this).closest('tr'));
})
.find("[name='betting-amount']").keyup();
});
Take a look at this fiddle for a demo.