I have an ajax call in my add in which it should create or update the table in excel. If table is already exists, it should remove the rows and add the new results.
When deleting the rows in loop, it is deleting some rows and then I am getting following error:
Debug info: {"code":"InvalidArgument","message":"The argument is invalid or missing or has an incorrect format.","errorLocation":"TableRowCollection.getItemAt"}
My ajax call in my excel web add-in looks like this:
$.ajax({
//....
}).done(function (data) {
Excel.run(function (ctx) {
var odataTable = ctx.workbook.tables.getItemOrNullObject("odataTable");
//rows items are not available at this point, that is why we need to load them and sync the context
odataTable.rows.load();
return ctx.sync().then(function () {
if (odataTable.rows.items == null) {
odataTable.delete();
odataTable = ctx.workbook.tables.add('B2:G2', true);
odataTable.name = "odataTable";
} else {
console.log("Rows items:" + odataTable.rows.items.length);
odataTable.rows.items.forEach(function (item) {
console.log("Removing row item: " + item.values);
item.delete();
});
console.log("rows cleaned");
}
}).then(function () {
//add rows to the table
});
}).then(ctx.sync);
}).catch(errorHandler);
}).fail(function (status) {
showNotification('Error', 'Could not communicate with the server. ' + JSON.stringify(status));
}).always(function () {
$('#refresh-button').prop('disabled', false);
});
The idea of the iterable collections is that they consist of different items. Once you remove something from these items in a not appropriate way, the collection stops being a collection. This is because they are implemented as a linked list, in which every unit knows only the next unit. https://en.wikipedia.org/wiki/Linked_list
In your case, you are deleting wtih a for-each loop. After the first deletion, the collection is broken. Thus, you need another approach.
Another approach:
Start looping with a normal for loop. Reversed.
E.g.:
for i = TotalRows to 1 i--
if row(i) something then delete
This has already been answered but I recently solved this issue myself and came here to see if anyone had posted a question about it.
When you delete a row, Excel will reorder the row index for each row: i.e. when you delete row 1, row 2 becomes row 1 and all other rows get shifted down 1 index. Because these deletions are pushed to a batch to be completed, when the second deletion is executed, your second row has become row one, so it actually skips the second row and executes what you think is your third row.
If you start from the last row and work backwards, this reordering doesn't occur and neither does the error.
For completeness sake the above example would become:
return ctx.sync().then(function () {
if (odataTable.rows.items == null) {
odataTable.delete();
odataTable = ctx.workbook.tables.add('B2:G2', true);
odataTable.name = "odataTable";
} else {
console.log("Rows items:" + odataTable.rows.items.length);
for (let i = odataTable.rows.items.length -1; i >= 0; i--) // reverse loop
odataTable.rows.items[i].delete();
}
console.log("rows cleaned");
}
Related
my problem is, that I have a table which should update everytime when the user chooses something from a dropdown component. The problem now is that my table updates "too late" in the frontend. So when the user chooses an option for the first time nothing will happen. Then when the user chooses an option for the second time from the dropdown component, the table will show the data from the option he has picked before. If the user chooses an option for the 3rd time, the table will show the data from the second one and so on.
So how can I fix this? I work with ReactJS and Semantic UI
My Code:
This renders the Row for the existing data
renderTableData() {
return this.state.songs.map((song, index) => {
const { id, nr, songname, link } = song
return (
<Table.Row key={id}>
<Table.Cell>{nr}</Table.Cell>
<Table.Cell>{songname}</Table.Cell>
<Table.Cell>{link}</Table.Cell>
</Table.Row>
)
})
}
The Code in the main render() function of React (Its shown correctly, expect that the data is "outdated":
`<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={1}>Nr</Table.HeaderCell>
<Table.HeaderCell width={2}>Songname</Table.HeaderCell>
<Table.HeaderCell width={1}>Link</Table.HeaderCell>
</Table.Row>
</Table.Header>
{this.renderTableData()}
</Table>`
The code when the option from the dropdown gets changed:
onChangeDropdown(e) {
this.setState({game: e.target.textContent}, ()=>{
this.state.songs.length = 0;
for(var i = 0; i< this.state.musicData.length;i++){
if(this.state.musicData[i].game == this.state.game){
for(var j = 0; j<this.state.musicData[i].songs.length;j++){
this.state.songs.push({id: j+1, nr: j+1, songname: this.state.musicData[i].songs[j].name, link: this.state.musicData[i].songs[j].link})
}
break;
}
}
this.renderTableData()
})
}
The game variable in this.setState is correct and also the for-loop works as expected when the user changes the dropdown option, I already checked it with the debugger
I hope you can help me out there, ty
is not that is updating too late, is that you are mutating the state without using setState so React doesn't know what changed, you should refactor your code to always use setState to update the state, not push, something like this:
onChangeDropdown(e) {
this.setState((currentState) => {
const newSongs = [];
const game = e.target.textContent;
musicData.forEach((data) => {
if (data.game === game) {
musicData.songs.forEach((song, index) => {
newSongs.push({
id: index + 1,
nr: index + 1,
songname: song.name,
link: song.link,
});
});
}
});
return {
...currentState,
game,
songs: newSongs,
};
});
}
I changed your for loops to use forEach, less complexity, easier to read
Here is what I did:
create a empty array to store the selected songs (newSongs)
loop all the music data and then loop all the songs inside each item in music data
add the songs from the selected game into newSongs
return newSongs + game to update the selected game, ...currentState is to preserve the other parts of the state between changes
So every time the dropodown changes, I create a new array and run the logic
The setState callback can return an object to replace whole state, so before that you can do any calculation you need to.
Updating the state in React is asyncronous, that's one of the reasons you can't mutate the state directly and need to use setState any time you need to update it
I'm trying to build a db with bluebird and sqlite3 to manage a lot of "ingredients".
So far I've managed to parse a file and extrapolate some data from it using regex.
Every time a line matches the regex I want to search in the database if an element with the same name has already been inserted, if so the element is skipped, otherwise it must be inserted.
The problem is that some elements get inserted more than one time.
The code partially works, and I'm saying it partially works because if I remove the line of code where I check the existence of an element with the same name, the duplicate rows are much more.
Here's the piece of code:
lineReader.eachLine(FILE_NAME, (line, last, cb) => {
let match = regex.exec(line);
if (match != null) {
let matchedName = match[1].trim();
//This function return a Promise for all the rows with corresponding name
ingredientsRepo.getByName(matchedName)
.then((entries) => {
if (entries.length > 0) {
console.log("ALREADY INSERTED INGREDIENT: " + matchedName)
} else {
console.log("ADDING " + matchedName)
ingredientsRepo.create(matchedName)
}
})
}
});
I know I'm missing something about Promises but I can't understand what I'm doing wrong.
Here's the code of Both getByName(name) and create(name):
create(name) {
return this.dao.run(
`INSERT INTO ingredients (name) VALUES (?)`,
[name]
)
}
getByName(name) {
return this.dao.all(
'SELECT * FROM ingredients WHERE name == ?',
[name]
)
}
ingredientsRepo.getByName(matchedName) returns a promise, this means it's asynchronous. I am also guessing that ingredientsRepo.create(matchedName) is asynchronous, because you are inserting something into a DB.
Your loop doesn't wait for these async functions to complete. Hence the loop could already be on the 50th iteration, when the .then(...) is called for the first iteration.
So let's say the first 10 elements have the same name. Well, since the asynchronous functions take some time to complete, the name will not be inserted into the DB until say maybe the 20th iteration of the loop. So for the first 10 elements, the name does not exist within the DB.
It's a timing issue.
I am trying to iterate through and existing smartsheet to get the rowIds (which I need for setting parent/child relationships).
I am struggling :-(
Obviously I don't get the required syntax on this one. Here is what I tried:
var options = {
Id: *******************
};
var row_ids = '';
// get the sheet
smartsheet.sheets.getSheet(options)
.then(function(sheetinfo) {
// iterate through the rows array and build comma-delimited list of row ids
for (rowIds in sheetinfo) {
row_ids += row_ids.concat(', ');
}
});
console.log(row_ids);
Any help would be most appreciated.
Bowow99
In the Get Sheet response, sheetInfo contains a collection of rows and each row object in that collection contains the property id. So to successfully implement the scenario you've described, you need to loop through the sheetInfo.rows array, and for each row in that array, append the value of its id property (followed by a comma) to create the comma-delimited string that you're attempting to build. At the end of this loop, there'll be an extra comma at the end (following the last row id you added to the string) -- so you'll need to remove it using the substring function.
The following code does what I've described above. Please note, I'm not a JavaScript expert -- so there may be a more efficient way to do this...but this code gets you the end result you're after: a comma-delimited string of the row Ids within the specified sheet.
var options = {
id: 8337220210845572 // Id of Sheet
};
var row_ids = '';
// Get sheet
smartsheet.sheets.getSheet(options)
.then(function(sheetInfo) {
// iterate through rows in the sheet and build comma-delimited string of row ids
for (var i=0; i < sheetInfo.rows.length; i++) {
row_ids += sheetInfo.rows[i].id + ',';
}
// remove the final (excess) comma from the end of the row_ids string
row_ids = row_ids.substring(0, row_ids.length-1);
console.log(row_ids);
})
.catch(function(error) {
console.log(error);
});
I'm having trouble to extract values from an excel file with the office.js add in I'm writing.
The add in shall help my colleagues to prepare report sheets for each teacher.
It is supposed to filter the corresponding courses from the master table and send the data to the next processing step (create word files for each teacher).
I've tried filtering ranges with autofilter and creating a table with the data, but it seems that no code is executed after return context.sync()
I've read the official tutorial and some of the code on buildingofficeaddins.com but my function never executes the code after "return context.sync()"
function mselectTeacher(teachers) {
Excel.run(function (context) {
var sheet = context.workbook.worksheets.getActiveWorksheet();
var lfv = sheet.tables.add("A1:M211", true);
var wsy = lfv.columns.getItem("WS/SS");
var studium = lfv.columns.getItem("Studium");
// some more colums
wsy.load("values");
studium.load("values");
return context.sync()
.then(function () {
//I actually want to filter the rows by teacher,
//this is only for testing
for (var i = 1; i < 20; i++) {
console.log(wsy[i] + "," + studium[i]);
}
});
});
}
Is the problem, that I'm calling Excel.run from within another function?
Could you try this?
function mselectTeacher(teachers) {
Excel.run(function (context) {
var sheet = context.workbook.worksheets.getActiveWorksheet();
var lfv = sheet.tables.add("A1:M211", true);
var wsy = lfv.columns.getItem("WS/SS");
var studium = lfv.columns.getItem("Studium");
// some more colums
wsy.load("values");
studium.load("values");
context.sync()
//I actually want to filter the rows by teacher,
//this is only for testing
for (var i = 1; i < 20; i++) {
console.log(wsy[i] + "," + studium[i]);
}
});
}
I tried your code in ScriptLab on excel web and saw "The requested resource doesn't exist"error in F12
I think it's due to no columns named "WS/SS" "Studium" in your new added table. It works after i changed the columns name with "WS/SS" "Studium"
I am doing e2e testing on a site that contains a table which I need to iterate "until" finding one that doesn't fail when I click on it.
I tried it using filter and it is working:
this.selectValidRow = function () {
return Rows.filter(function (row, idx) {
row.click();
showRowPage.click();
return errorMessage.isDisplayed().then(function (displayed) {
if (!displayed) {
rowsPage.click(); // go back to rows Page, all the rows
return true;
}
});
}).first().click();
};
The problem here is that it is iterating all available rows, and I only need the first one that is valid (that doesn't show an errorMessage).
The problem with my current approach is that it is taking too long, as my current table could contain hundreds of rows.
Is it possible to filter (or a different method) and stop iterating when first valid occurrence appears?, or could someone come up with a better approach?
If you prefer a non-protractor approach of handling this situation, I would suggest async.whilst. async is a very popular module and its highly likely that your application is using it. I wrote below code here in the editor, but it should work, you can customize it based on your needs. Hopefully you get an idea of what I'm doing here.
var found = false, count = 0;
async.whilst(function iterator() {
return !found && count < Rows.length;
}, function search(callback) {
Rows[count].click();
showRowPage.click();
errorMessage.isDisplayed().then(function (displayed) {
if (!displayed) {
rowsPage.click(); // go back to rows Page, all the rows
found = true; //break the loop
callback(null, Rows[count]); //all good, lets get out of here
} else {
count = count + 1;
callback(null); //continue looking
}
});
}, function aboutToExit(err, rowIwant) {
if(err) {
//if search sent an error here;
}
if(!found) {
//row was not found;
}
//otherwise as you were doing
rowIwant.click();
});
You are right, filter() and other built-in Protractor "functional programming" methods would not solve the "stop iterating when first valid occurrence appears" case. You need the "take some elements while some condition evaluates to true" (like the itertools.takewhile() in Python world).
Fortunately, you can extend ElementArrayFinder (preferably in onPrepare()) and add the takewhile() method:
Take elements while a condition evaluates to true (extending ElementArrayFinder)
Note that I've proposed it to be built-in, but the feature request is still open:
Add takewhile() method to ElementArrayFinder