I have below functions that I am trying to use an async function to check if an item Id was already added into a grid. But, I am stuck at the for loop "processFindCode" function, it always returns false. How to make the checking routine work and continue to addItem function?
My objective,
1. a loop up event pass in selected array values
2. get needed info array via web api
3. process each of array item
4. validate if the item.Id_item_code already exists in detail grid, if not add a new item into the grid
sorry for the lengthy code, I am considered new to js. please also advise the best way to achieve my objective.
Thank in advance
var theList = this.value;
if (theList == "") {
return true;
}
// Set up aysnc promise function
async function processAPIResult() {
let result;
let promises = []; //setup a promises aray
// Loop thru Lookup selections
// results store in the promises aray
theList.split(',').forEach(function (kId, k) {
promises.push(make_api_call(kId));
})
result = await Promise.all(promises); // async await
return result;
}
// Call API Function
function make_api_call(id) {
return ($.get(ew.API_URL + "GetQuotationDetail/" + encodeURIComponent(id)));
}
function delay() {
return new Promise(resolve => setTimeout(resolve, 300));
}
async function addItem(kcode) {
var ridx = $("#key_count_fs_invoicedtlgrid")[0].value; // get actual table row
// Get serices information from API
var tQty = 1;
if (ridx == 1 && $("#x1_Id_item_code")[0].value == "") {
$("#x" + ridx + "_Id_item_code").value(kcode);
$("#x" + ridx + "_qty").value(tQty);
$("#x" + ridx + "_Id_item_code").select();
$("#x" + ridx + "_Id_item_code").change();
//$("#x" + ridx + "_Id_quotattoindetl").value(Id_quotationDetail); // store quotation detail Id
} else {
// 2nd row onward
// when it is available but it it is not empty, create a new row
if (typeof $("#x" + ridx + "_Id_item_code") === 'undefined' || $("#x" + ridx + "_Id_item_code")[0] != "") {
ew.addGridRow('#tbl_s_invoicedtlgrid');
}
ridx = $("#key_count_fs_invoicedtlgrid")[0].value;
var c_part_code = $("#x" + ridx + "_Id_item_code")[0];
var c_qty = $("#x" + ridx + "_qty")[0];
c_part_code.value = kcode;
c_qty.value = fmtDecimal(tQty, 'qty');
$("#x" + ridx + "_Id_item_code").select();
$("#x" + ridx + "_Id_item_code").change();
//$("#x" + ridx + "_Id_quotattoindetl").value(Id_quotationDetail); // store quotation detail Id
// trigger onChange to updateAmount() function;
$("#x" + ridx + "_qty").change();
}
}
async function processFindCode(code) {
found = false;
var rCnt = $("#key_count_fs_invoicedtlgrid")[0].value;
for (j = 1; j < rCnt + 1; j++) {
// loop thru row and col
if ($("#x" + j + "_item_code").val() != null) {
if ($("#x" + j + "_item_code").val() == kcode) {
if ($("#x" + j + "_item_code")[0].style.display != "none") { // visible row only. Deleted row's display is set to hidden.
found = true;
}
break; // exit loop column
}
}
if (found) {
break; // exit loop row
}
} // end for loop rCnt
isFound = found;
return isFound;
}
async function processItemCode(code) {
// notice that we can await a function
// that returns a promise
let isFound = await processFindCode(code);
await delay();
if (!isFound) {
await addItem(code);
}
console.log('Done! ' + code);
//await delay();
//console.log(item.Id_item_code);
}
async function processAPIitem(item) {
for (const itm of item) {
await processItemCode(itm.Id_item_code);
}
//console.log('Done! '+ apiitem.Id_item_code);
}
// main async task to get results
//-------------------------------
async function doTask() {
//get result form API function
let result = await processAPIResult();
//process each API array
for (const item of result) {
await processAPIitem([item]);
}
}
doTask();
Simple answer: $item_code.hide() hides; it isn't a test. Use $item_code.is(':visible') instead.
function processFindCode(code) {
var found = false;
var rCnt = $('#key_count_fs_invoicedtlgrid').val();
for (var j=1; j<rCnt+1; j++) {
if ($('#x' + j + '_item_code').val() != null) {
if ($('#x' + j + '_item_code').val() == code) {
if ($('#x' + j + '_item_code').is(':visible')) {
found = true;
break;
}
}
}
}
return found;
}
Longer answer: Finding the desired element with $('#x' + j + '_item_code') is inefficient especially when performed three times. You can improve matters by leveraging the power of javascript/jQuery and write something like this.
function processFindCode(code) {
var $rows = $('#tbl_s_invoicedtlgrid tbody tr'); // select all table rows in the table's tbody (assumed selector) .
return $rows.get().reduce(function(bool, row) {
return bool || $("id*='_item_code'", row).filter(':visible').filter(function() {
return this.value === code;
}).length > 0;
}, false);
}
This is still not as efficient as it could be. The "id*='_item_code'" selector is pretty nasty but at least it only applies to one row at a time, not the whole DOM.
For vastly improved efficiency, give the item_code element class="item_code" and select with $(".item_code", row). The addItem() function would benefit greatly from the same approach.
Related
Below I have some code I have running for a spreadsheet. Right now it takes a min or two to run through the script. I was wondering if anyone has any suggestions on how to re-work my code to run a little faster.
What the code does is search on a tab in the sheet called "set up" for check-marked items in a list that I would like included in my "Master Sheet". Then go to my sheet which contains all of the information that I would like copied and pasted over according to what is check marked on my set-up page. Then copy and paste those line items to the master sheet.
function allToMaster(){
var sss = SpreadsheetApp.getActive();
var ssAll = sss.getSheetByName("FF All");
var ssMaster = sss.getSheetByName("FF Master");
var ssSetup = sss.getSheetByName("FF Setup");
ssMaster.clear();
var masterCounter = 2;
ssAll.getRange("P:P").clear();
var sourceRange = ssAll.getRange(1,1,1,15);
sourceRange.copyTo(ssMaster.getRange(1,1));
//get last row of FF All
var lastRowAll = ssAll.getLastRow();
var lastRowMaster = ssMaster.getLastRow();
ssAll.getRange("P2:P" + lastRowAll).setFormula("=index('FF Setup'!B:B,match(B2,'FF Setup'!C:C,0))");
ssMaster.setRowHeightsForced(2, 500, 26);
for (i=2;i<=lastRowAll;i++){
if (ssAll.getRange(i,1).getBackground() == "#a8d08d"){
var sourceRange = ssAll.getRange(i,1,1,15);
sourceRange.copyTo(ssMaster.getRange(masterCounter,1));
masterCounter++;
} else if (ssAll.getRange(i,1).getBackground() == "#e2efd9"){
var sourceRange = ssAll.getRange(i,1,1,15);
sourceRange.copyTo(ssMaster.getRange(masterCounter,1));
masterCounter++;
} else {
if (ssAll.getRange("P" + i).getValue() == true) {
var sourceRange = ssAll.getRange(i,1,1,15);
sourceRange.copyTo(ssMaster.getRange(masterCounter,1));
ssMaster.setRowHeightsForced(masterCounter, 1, 136);
masterCounter++;
}
}
}
ssAll.getRange("P:P").clear();
//Clear Empty Subtitles
var lastRowMaster = ssMaster.getLastRow();
for (i=2;i<=lastRowMaster;i++){
if (ssMaster.getRange(i,1).getBackground() == "#e2efd9"){
if(ssMaster.getRange((i+1),1).getBackground() == "#e2efd9" || ssMaster.getRange((i+1),1).getBackground() == "#a8d08d"){
ssMaster.deleteRow(i);
ssMaster.insertRowAfter(500);
i=i-1;
}
}
}
//Clear Empty Titles
var lastRowMaster = ssMaster.getLastRow();
for (i=2;i<=lastRowMaster;i++){
if (ssMaster.getRange(i,1).getBackground() == "#a8d08d"){
if(ssMaster.getRange((i+1),1).getBackground() == "#a8d08d"){
ssMaster.deleteRow(i);
ssMaster.insertRowAfter(500);
i=i-1;
}
}
}
//Find the row with "Delivery"
var deliveryRow = getRowOf("DELIVERY", "FF All", 1);
var sourceRange = ssAll.getRange(deliveryRow,1,(lastRowAll - deliveryRow + 1),15);
var masterCounter = ssMaster.getLastRow()
sourceRange.copyTo(ssMaster.getRange(masterCounter,1));
masterCounter = masterCounter + lastRowAll - deliveryRow - 2;
//.setFormula('=SUMA(J264:J275)');
// ssMaster.getRange(masterCounter, 10).setFormula("=sum(J2:J" + (masterCounter - 1) + ")");
//ssMaster.getRange(masterCounter, 11).setFormula("=sum(K2:K" + (masterCounter - 1) + ")");
//ssMaster.getRange(masterCounter, 13).setFormula("=sum(M2:M" + (masterCounter - 1) + ")");
//ssMaster.getRange(masterCounter, 15).setFormula("=M" + masterCounter + " - K" + masterCounter);
}
function getRowOf(value, sheet, col){
var dataArr = SpreadsheetApp.getActive().getSheetByName(sheet).getRange(4, col, 3500, 1).getValues();
for(var j = 0; j < dataArr.length; j ++){
var currVal = dataArr[j][0];
if(currVal == value){
return j+4;
break;
}
}
return 0;
}
You need to change the loops as they are doing several calls to Class SpreadsheetApp on each iteration.
Regarding the first loop,
for (i=2;i<=lastRowAll;i++){
if (ssAll.getRange(i,1).getBackground() == "#a8d08d"){
var sourceRange = ssAll.getRange(i,1,1,15);
sourceRange.copyTo(ssMaster.getRange(masterCounter,1));
masterCounter++;
} else if (ssAll.getRange(i,1).getBackground() == "#e2efd9"){
var sourceRange = ssAll.getRange(i,1,1,15);
sourceRange.copyTo(ssMaster.getRange(masterCounter,1));
masterCounter++;
} else {
if (ssAll.getRange("P" + i).getValue() == true) {
var sourceRange = ssAll.getRange(i,1,1,15);
sourceRange.copyTo(ssMaster.getRange(masterCounter,1));
ssMaster.setRowHeightsForced(masterCounter, 1, 136);
masterCounter++;
}
}
}
Instead of getting the background of one cell at a time (ssAll.getRange(i,1).getBackground()), before the loop get the backgrounds of all the cells before the loop, i.e.
const backgrounds = ssAll.getRange(2,1,lastRowAll).getBackgrounds();
then replace ssAll.getRange(i,1).getBackground() by backgrounds[i-1][0].
Do the something similar about ssAll.getRange("P" + i).getValue(), before the loop get the all values of the P column:
const values = ssAll.getRange("P" + i + ":P" + lastRowAll).getValues()
then replace ssAll.getRange("P" + i).getValue() by values[i-1][0]`.
It might be also possible to optimize further the first loop depending on if you really need to copy the ranges (besides values, include borders, background, notes, etc.) or if you only need the values.
Another option is to use the Advances Sheets Services but this implies to make a completely different implementation.
I'm doing RolePlay Character Sheets on a "Parent tab" I've called "MODEL", where I masterize my formulas.
I've created a second tab "Character1" and a third one "Character2". But when I try to use =QUERY or =TEXTFORMULA or whatever. It doesn't make the formulas to calculate on the actual spreadsheet, it just get the data from the "MODEL" tab.
My only way is actually to copy/past all my formulas, but if I do a mistake, I'll have to correct it in every spreadsheet every time.
Is that possible to have a formula which take the cell at:
MODELE!AE58
And automatically generate the same formulas in every tabs:
CHARACTER1!AE58
CHARACTER2!AE58
etc...
Sorry if its blur, I'm doing my best to explain.
simple
Try
function onEdit(e) {
var sh = e.source.getActiveSheet()
var rng = e.source.getActiveRange()
if (rng.getFormula() != '' && sh.getName() == 'MODEL') {
var excl = ['MODEL', 'OTHER'];//excluded sheets
SpreadsheetApp.getActiveSpreadsheet().getSheets().forEach(sh => {
if (!~excl.indexOf(sh.getSheetName())) {
sh.getRange(rng.getA1Notation()).setFormula(rng.getFormula())
}
})
}
}
when you change a formula in MODEL, this will also change in other tabs excepts excluded ones
multiple
If you edit the formulas by dragging them into the MODEL sheet, use this one which allows you to edit all the formulas at once
function onEdit(e) {
var sh = e.source.getActiveSheet()
if (sh.getName() != 'MODEL') return;
for (var i = e.range.rowStart; i <= e.range.rowEnd; i++) {
for (var j = e.range.columnStart; j <= e.range.columnEnd; j++) {
if (sh.getRange(i, j).getFormula() != '') {
var excl = ['MODEL', 'OTHER'];//excluded sheets
SpreadsheetApp.getActiveSpreadsheet().getSheets().forEach(child => {
if (!~excl.indexOf(child.getSheetName())) {
child.getRange(sh.getRange(i, j).getA1Notation()).setFormula(sh.getRange(i, j).getFormula())
}
})
}
}
}
}
global
Il you need to reset all formulas, enable google sheets api and try
function onOpen() {
SpreadsheetApp.getUi().createMenu('⇩ M E N U ⇩')
.addItem('👉 Apply all formulas from MODEL to all tabs', 'spreadFormulas')
.addToUi();
}
function spreadFormulas() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('MODEL')
if (sh.getName() != 'MODEL') return;
var data = [];
var formulas = sh.getRange(1, 1, sh.getLastRow(), sh.getLastColumn()).getFormulas()
for (var i = 0; i < formulas.length; i++) {
for (var j = 0; j < formulas[0].length; j++) {
if (formulas[i][j] != '') {
var excl = ['MODEL', 'OTHER'];//excluded sheets
SpreadsheetApp.getActiveSpreadsheet().getSheets().forEach(child => {
if (!~excl.indexOf(child.getSheetName())) {
data.push({
range: `${child.getName()}!${columnToLetter(+j + 1) + (+i + 1)}`,
values: [[`${formulas[i][j]}`]],
})
}
})
}
}
}
if (data.length) {
var resource = {
valueInputOption: 'USER_ENTERED',
data: data,
};
try { Sheets.Spreadsheets.Values.batchUpdate(resource, ss.getId()); } catch (e) { console.log(JSON.stringify(e)) }
}
}
function columnToLetter(column) {
var temp, letter = '';
while (column > 0) {
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
if your sheet is called MODELE try on some other sheet just:
=MODELE!AE58
for array it would be:
={MODELE!AE58:AE100}
also take a look into "Named Ranges" - maybe you will find it more handy
Strange phenomenon. socketio
SERVER
i have that code...
socket.on('disconnect', function(){
console.log('user disconnected');
for(var i=0; i<stores.length; i++ ){
var c = stores[i];
if(c.socketid == socket.id){
stores.splice(i,1);
break;
}
}
});
everything goes well with the .splice. If i print the stores array from elsewhere, it displayed correct ...but in this situation
socket.on('disconnect', function(){
console.log('user disconnected');
for(var i=0; i<stores.length; i++ ){
var c = stores[i];
if(c.socketid == socket.id){
for(var i=0; i<stores.length;i++){
console.log(i+" one"+stores[i].name+"-"+stores[i].id)
}
stores.splice(i,1);
for(var i=0; i<stores.length;i++){
console.log(i+" two"+stores[i].name+"-"+stores[i].id)
}
break;
}
}
});
the array has not lost its values, i visit the page from my browser(i connected),
socket.on('storelogged', function (msg){
var storeInfo = new Object();
storeInfo.name = msg.name;
storeInfo.id = msg.id;
storeInfo.socketid = socket.id;
stores.push(storeInfo);
console.log(msg.name + " has connected with " + msg.id + " id." );
});
so, stores pushed. But when i disconnected in the second situation of socket.on('disconnect',callback) the stores array still contains the values( in other words, splice dont work )
comment for giving, much and better information. Also you can test it and see the results
Your embedded for loops are overwriting the i variable from the top level for loop.
Either use let instead of var as in for (let i = 0; ....) for all your for loops so each has a different locally scoped value of i and the inner loops won't overwrite the outer loops or use a different variable name for the embedded for loops or use .forEach() which creates a new variable for the index.
In addition, after you call .splice() on the array you are iterating, you have decrement the current array index from your for loop or you will skip looking at one of the values in the array because the .splice() moved it down into the index spot that you just removed and that your for loop has already iterated.
For example, you can change variable names of the inner for loops like this:
socket.on('disconnect', function () {
console.log('user disconnected');
for (var i = 0; i < stores.length; i++) {
var c = stores[i];
if (c.socketid == socket.id) {
for (var j = 0; j < stores.length; j++) {
console.log(j + " one" + stores[j].name + "-" + stores[j].id)
}
stores.splice(i, 1);
// make sure not to skip the value we just moved into the i slot in the array
i--;
for (var k = 0; i < stores.length; k++) {
console.log(k + " two" + stores[k].name + "-" + stores[k].id)
}
break;
}
}
});
Or, you can use let for the for loops:
socket.on('disconnect', function () {
console.log('user disconnected');
for (var i = 0; i < stores.length; i++) {
var c = stores[i];
if (c.socketid == socket.id) {
for (let i = 0; i < stores.length; i++) {
console.log(i + " one" + stores[i].name + "-" + stores[i].id)
}
stores.splice(i, 1);
// make sure not to skip the value we just moved into the i slot in the array
i--;
for (let i = 0; i < stores.length; i++) {
console.log(i + " two" + stores[i].name + "-" + stores[i].id)
}
break;
}
}
});
Or, you can use .forEach():
socket.on('disconnect', function () {
console.log('user disconnected');
for (var i = 0; i < stores.length; i++) {
var c = stores[i];
if (c.socketid == socket.id) {
stores.forEach(function(item, index) {
console.log(index + " one" + item.name + "-" + item.id)
});
stores.splice(i, 1);
// make sure not to skip the value we just moved into the i slot in the array
i--;
stores.forEach(function(item, index) {
console.log(index + " one" + item.name + "-" + item.id)
});
break;
}
}
});
I'm having some difficulty figuring out how to programatically modify the sort definition that is sent to the server when a user clicks on a column to sort it. I have added a onSortCol function to my grid configuration. In that function, I need to check whether the "Id" column is in any sort position other than the last position. If it is, it should be removed.
Here is what I have tried:
onSortCol: function (index, iCol, sortOrder) {
var grid = $(this);
var rawSorts = index.split(",");
if (rawSorts.length > 1) {
var idFieldIndex = -1;
var processedSorts = [];
for (i = 0; i < rawSorts.length; i++) {
var currentSort = rawSorts[i].match(/[^ ]+/g);
if (idFieldIndex === -1 && currentSort[0].toUpperCase() === "ID") {
idFieldIndex = i;
}
processedSorts.push({
field: currentSort[0],
direction: currentSort[1] || sortOrder
})
}
if (idFieldIndex !== -1) {
processedSorts.splice(idFieldIndex, 1);
for (i = 0; i < processedSorts.length; i++) {
if (i + 1 < processedSorts.length) {
grid.sortGrid(processedSorts[i].field + " " + processedSorts[i].direction);
}
else {
grid.setGridParam("sortorder", processedSorts[i].direction);
grid.sortGrid(processedSorts[i].field + " ", true);
}
}
return "stop";
}
}
}
The most simple implementation seems to me the following: you don't use any sortname in the grid initially and you sort by Id on the server side if sidx is empty. It seems the only what you need to do to implement your requirements.
Suppose we've a grid with sortable columns.
If a user clicks on a column, it gets sorted by 'asc', then if a user clicks the column header again, it gets sorted by 'desc', now I want similar functionality when a user clicks the column third time, the sorting is removed, i.e. returned to previous style, the css is changed back to normal/non-italic etc?
Today I was trying to achieve same thing. I've skimmed through SlickGrid code but didn't find any 'Reset' function. So, I've tweaked the slick.grid.js v2.2 a little bit.
Basically you just need to add a new array - 'latestSortColumns' which will store sort columns state (deep copy of internal SlickGrid sortColumns array).
var latestSortColumns = [];
Optionally add new setting to opt-in for sort reset.
var defaults = {
(...),
autoResetColumnSort: false
};
Change setupColumnSort to reset sort after third click on column's header.
function setupColumnSort() {
$headers.click(function (e) {
// temporary workaround for a bug in jQuery 1.7.1 (http://bugs.jquery.com/ticket/11328)
e.metaKey = e.metaKey || e.ctrlKey;
if ($(e.target).hasClass("slick-resizable-handle")) {
return;
}
var $col = $(e.target).closest(".slick-header-column");
if (!$col.length) {
return;
}
var column = $col.data("column");
if (column.sortable) {
if (!getEditorLock().commitCurrentEdit()) {
return;
}
var sortOpts = null;
var i = 0;
for (; i < sortColumns.length; i++) {
if (sortColumns[i].columnId == column.id) {
sortOpts = sortColumns[i];
sortOpts.sortAsc = !sortOpts.sortAsc;
break;
}
}
**if ((e.metaKey || (options.autoResetColumnSort && latestSortColumns[i] != null && latestSortColumns[i].sortAsc === !column.defaultSortAsc)) && options.multiColumnSort) {**
if (sortOpts) {
sortColumns.splice(i, 1);
**latestSortColumns.splice(i, 1);**
}
}
else {
if ((!e.shiftKey && !e.metaKey) || !options.multiColumnSort) {
sortColumns = [];
}
if (!sortOpts) {
sortOpts = { columnId: column.id, sortAsc: column.defaultSortAsc };
sortColumns.push(sortOpts);
} else if (sortColumns.length == 0) {
sortColumns.push(sortOpts);
}
}
setSortColumns(sortColumns);
if (!options.multiColumnSort) {
trigger(self.onSort, {
multiColumnSort: false,
sortCol: column,
sortAsc: sortOpts.sortAsc}, e);
} else {
trigger(self.onSort, {
multiColumnSort: true,
sortCols: $.map(sortColumns, function(col) {
return {sortCol: columns[getColumnIndex(col.columnId)], sortAsc: col.sortAsc };
})}, e);
}
}
});
}
Store new state after every sort change in latestSortColumns:
function setSortColumns(cols) {
sortColumns = cols;
var headerColumnEls = $headers.children();
headerColumnEls
.removeClass("slick-header-column-sorted")
.find(".slick-sort-indicator")
.removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");
$.each(sortColumns, function(i, col) {
if (col.sortAsc == null) {
col.sortAsc = true;
}
var columnIndex = getColumnIndex(col.columnId);
if (columnIndex != null) {
headerColumnEls.eq(columnIndex)
.addClass("slick-header-column-sorted")
.find(".slick-sort-indicator")
.addClass(col.sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");
}
});
**for (var i = 0; i < sortColumns.length; i++)
latestSortColumns[i] = { columnId: sortColumns[i].columnId, sortAsc: sortColumns[i].sortAsc };**
}
That's all, should work now.
This is a function I call to reset all columns back to their original order.
It requires one of columns to be set up as not sortable.
In the example below I use the first column of the table, columns[0], which has the field id "locus".
function removeSorting() {
columns[0].sortable = true;
$('.slick-header-columns').children().eq(0).trigger('click');
columns[0].sortable = false;
// clear other sort columns
grid.setSortColumns( new Array() );
}
Then in the typical dataView.sort() function you make an exception for this column:
grid.onSort.subscribe(function(e,args) {
var cols = args.sortCols;
dataView.sort(function (dataRow1, dataRow2) {
for( var i = 0; i < cols.length; i++ ) {
var field = cols[i].sortCol.field;
// reset sorting to original indexing
if( field === 'locus' ) {
return (dataRow1.id > dataRow2.id) ? 1 : -1;
}
var value1 = dataRow1[field];
var value2 = dataRow2[field];
if( value1 == value2 ) continue;
var sign = cols[i].sortAsc ? 1 : -1;
return (value1 > value2) ? sign : -sign;
}
return 0;
});
});
Will the table always be sorted in some order on some column?
If not then you'll have to store a lot of state the first time a column is clicked just in case you want to restore it after a third click.