DispatchEvent in javascript for loop - javascript

I have created an event so that I can add listeners for when an event occurs. There will be different scenarios that trigger the event and therefore would be tracking what triggered the event and handle it appropriately. However, I tried adding the dispatchEvent within a function that occurs within a loop and I am receiving an InvalidState error. Anyone cares to explain what causes this error and why it is happening here. You can see my code below
//create event
var event = new Event('tableRowAdded');
//get element reference
var table = document.getElementById('dataTable');
//set model
var model = [{jsonData},{jsonData},{jsonData}];
//validate model and then start creating table
function addRowsToTable(DOMTable, rowData){
//stop execution if rowdata null
if(rowData == null || DOMTable == null){
return;
}
var currentRowCount = DOMTable.rows.length;
//add empty row to table
var tr = DOMTable.insertRow(currentRowCount);
tr.id = "data_row_" + currentRowCount;
//add columns to table
//first cell is used for error data
var td = tr.insertCell(0);
//traineeCode
td = tr.insertCell(1);
td.innerHTML = rowData.TRAINEE_CODE; //this is one of the json keys within the json data
}
function populateTableFromJSON(){
var count = model.length;
//loop over json array to populate table
for(i = 0; i < count; i++){
addRowsToTable(table, model[i]);
//dispatch event occured
table.dispatchEvent(event);
}
}
This is the error received
Uncaught InvalidStateError: Failed to execute 'dispatchEvent' on 'EventTarget': The event is already being dispatched.

Presumably you need to change:
table.dispatchEvent(event);
to
table.dispatchEvent(new Event('tableRowAdded'));

Very messed up the example you posted.
You're calling a method where you perform object validation:
if(rowData == null || DOMTable == null){
return;
}
but at the same time you're ignoring the validation trying to dispatch the event anyway on the next line:
table.dispatchEvent(event);
The event object in javascript have some properties changed after being dispatched that's because the event should reflect its current state at several phases. Try to call this way:
table.dispatchEvent(new Event('tableRowAdded'));

I know this question is a little dated, but I thought I would at least answer it incase someone else stumbles upon this post.
The reason you are getting the error is because you are trying to dispatch the event to the same object (the table) over and over again. Try dispatching it to each row.
EDIT: Here's your code modified. I moved the dispatch inside the loop where the rows are added and added an event listener to the table.
//create event
var event = new Event('tableRowAdded');
//get table
var table = document.getElementById('dataTable');
// set event listener
table.addEventListener("tableRowAdded", function(e) {
handler code
});
//set model
var model = [{jsonData},{jsonData},{jsonData}];
//validate model and then start creating table
function addRowsToTable(DOMTable, rowData){
//stop execution if rowdata null
if(rowData == null || DOMTable == null){
return;
}
var currentRowCount = DOMTable.rows.length;
//add empty row to table
var tr = DOMTable.insertRow(currentRowCount);
tr.id = "data_row_" + currentRowCount;
//add columns to table
//first cell is used for error data
var td = tr.insertCell(0);
//traineeCode
td = tr.insertCell(1);
td.innerHTML = rowData.TRAINEE_CODE; //this is one of the json keys within the json data
// dispatch event
tr.dispatchEvent(event);
}
function populateTableFromJSON(){
var count = model.length;
//loop over json array to populate table
for(i = 0; i < count; i++){
addRowsToTable(table, model[i]);
}
}

Related

Element not found by id in DataTable

I am updating cells of dataTable very frequently using id as selector as each table cell that has to be updated already has unqiue id assigned. Below is the code
agentStatSocket.onmessage = function(e){
data = JSON.parse(e.data)
//console.log(data);
for(var i=0; i<data.length; i++){
try {
var inboundTd = data[i]['id'] + '-inbound';
var outboundTd = data[i]['id'] + '-outbound';
if (data[i]['inboundCalls'] != 0) {
document.getElementById(inboundTd).innerHTML = data[i]['inboundCalls'];
}
if (data[i]['outboundCalls'] != 0) {
document.getElementById(outboundTd).innerHTML = data[i]['outboundCalls'];
}
} catch (error) {
console.log(data[i]);
}
}
};
I am getting Uncaught TypeError: document.getElementById(...) is null but that id is available in the dom.
I tried to catch error so that at least my loop shouldn't stop and i can get all that data using console.log(data[i) which is not present in the table. To my surprise there were at least 10 objects printed in console. This cannot happen because i can see the previous data is present in table. When i commented out the DataTable() function and reloaded the page i didn't see any error.
Can you please tell me how to update table cells in DataTable as i need Search , Sort and Pagination functionality of DataTable ?

Getting this error Uncaught RangeError: Maximum call stack size exceeded

I am getting this error while uploading the CSV files in my website.
Screenshot attached below.
I can see my data of csv file while debugging but it is stopping me to proceed further but i am not able to get this error, I have searched that on google to but they are not relevant to this.
I am using the library
https://d3js.org/d3-dsv.v1.min.js
The code I am trying to upload file is below.
function file(event){
var uploadFileEl = document.getElementById('upload');
if(uploadFileEl.files.length > 0){
var reader = new FileReader();
reader.onload = function(e) {
fileProcess(reader.result.split(/\[\r\n\]+/));
}
reader.readAsText(uploadFileEl.files\[0\]);
}
}
function fileProcess(data) {
var lines = data;
//Set up the data arrays
var time = \[\];
var data1 = \[\];
var data2 = \[\];
var data3 = \[\];
var headings = lines\[0\].split(','); // Splice up the first row to get the headings
var headerCheckbox = document.getElementById('includeHeader');
if(headerCheckbox.checked == true){
for (var j=1; j<lines.length; j++) {
var values = lines\[j\].split(','); // Split up the comma seperated values
// We read the key,1st, 2nd and 3rd rows
time.push(values\[0\]); // Read in as string
// Recommended to read in as float, since we'll be doing some operations on this later.
if (values\[0\] =="" || values\[0\] == null )
{
delete values\[0\];
delete values\[1\];
delete values\[2\];
delete values\[3\];
}
else{
data1.push(parseFloat(values\[1\]));
data2.push(parseFloat(values\[2\]));
data3.push(parseFloat(values\[3\]));
}
}
The error I am getting is on this line
fileProcess(reader.result.split(/[\r\n]+/));
What could be the reason for this.
That error means that you are filling the call stack.
If you call a function that returns a function recursively and doesn't have a stop condition or it never fulfils it's stop condition that error will be throwed.
It appears that fileProcess(reader.result.split(/[\r\n]+/)) it's being called recursively and it's filling the call stack.
Don't know exactly where, as I don't see any recursive calls in the code you posted, so I can't help you farer, but I hope this can shed light into your problem.
P.S: If you think there's some relevant extra code that you didn't post and edit your question leave me a comment and I'll update my answer aswell.

MS Word API: table values not set

Using the Office JavaScript API, I am trying to fill a selected table using the following code:
Word.run(function (context) {
var table = context.document.getSelection().parentTable;
context.load(table);
return context.sync()
.then(function () {
if (table.isNullObject == true || !table) {
console.log("selection ist not table");
errorHandler("selection is not a table");
} else {
// loop over table
for (var row = 0; row < table.values.length; row++) {
for (var column = 0; column < table.values[row].length; column++) {
console.log(table.values[row][column]);
table.values[row][column] = "Test " + row + " " + column;
}
context.sync().then(function () {
console.log("done");
}).catch(function (e) {
console.log(e);
});
}
}
});
});
The scripts runs fine, the table object exists, the values are logged and the final "done" too. But the table stays as it is - no update of the values. What am I missing?
A few comments about the code you've posted:
Checking for isNullObject on the table object like your code is doing is not effective, because the lines above that will have already thrown an error if the parentTable doesn't exist.
Error handling (catch statement) should be located immediately after the Word.run -- that way it'll catch any error that occurs inside the Word.run.
Instead of loading the entire table object like you are, you should only load the properties that you need (in this case, values and rows/items/cells/items/body).
To set a cell value, use the insertText method on the body of the cell that you want to update.
The following code sample incorporates this feedback, and should successfully update the values in your table.
Word.run(function (context) {
var table = context.document.getSelection().parentTable;
table.load("values, rows/items/cells/items/body");
return context.sync()
.then(function () {
for (var row = 0; row < table.values.length; row++) {
for (var column = 0; column < table.values[row].length; column++) {
console.log(table.values[row][column]);
table.rows.items[row].cells.items[column].body.insertText("Test " + row + " " + column, "Replace");
}
}
return context.sync()
.then (function() {
console.log("Done");
});
});
}).catch(function (e) {
console.log(e);
});
Great question #BernhardWebstudio! To explain why your specific code doesn't work is because the add-in code is invoked in a separate process from the Office Application and it must make 'requests' to actually acquire the data. This is similar to asking for attributes within a REST or OData call. (e.g. $select)
What does this mean for you? Well with a just slight alteration to your code you just need to request what properties of the table you want to load. Since you only care about the values of the table, it would be easy to do this with this type of code. If you notice I also have a commented out line that is table.load("[Property Name]"); to use the Office Apps Object model as proxy objects as well. Kim has pointed out some other really good suggestions as well. Cheers and Happy Coding!
Word.run(function (context) {
let table = context.document.getSelection().parentTable;
//table.load("values");
context.load(table, "values");
//...

How do I submit an array from the client to a server (ExpressJs) using AJAX?

I'm working on a web application in which a user can drag and drop div elements, whose content is generated from a database, into their preferred order. I want the user to be able to, when they are finished arranging the divs, submit their list (in order) to the server and store the new order in a table. I'm using AJAX to do this, however I'm not sure if that is necessary/the proper way to do this, since I don't need to asynchronously update the page (I just need to submit the data). I've tried a variety of methods to get my array to the server in a usable format (it needs to be iterable and allow for element locations to be compared). I have tried using JSON.stringify/parse, creating a custom object, simply submitting the array on its own, and so on. Here is my code, the most relevant bits are towards the bottom by the makeRouteArray function. Ideally to accomplish this I would like to use just JavaScript (no jQuery). Finally, please excuse my messy code, I'm learning.
// get two groups of elements, those that are draggable and those that are drop targets
let draggable = document.querySelectorAll('[draggable]');
let targets = document.querySelectorAll('[data-drop-target]');
// div immediately surrounding bus routes
var busList = document.getElementById("bus-list");
const button = document.getElementById("button");
// store the id of the draggable element when drag starts
function handleDragStart(e) {
e.dataTransfer.setData("text", this.id); // sets 'text' value to equal the id of this
this.classList.add("drag-start"); // class for styling the element being dragged, sets opacity
}
function handleDragEnd(e) {
e.target.classList.remove('drag-start');
}
function handleDragEnterLeave(e) {
// should provide visual feedback to user?
}
// handles dragover event (moving your source div over the target div element)
// If drop event occurs, the function retrieves the draggable element’s id from the DataTransfer object
function handleOverDrop(e) {
e.preventDefault();
var draggedId = e.dataTransfer.getData("text"); // retrieves drag data (DOMString) for specified type
var draggedEl = document.getElementById(draggedId);
draggedEl.parentNode.insertBefore(draggedEl, this); // inserts element being dragged into list
var draggedArray = Array.from(draggedEl.parentNode.children); // creates new array which updates current location of each route
e.target.classList.remove('drag-start'); // sets opacity back to 1
// if (e.type === "drop") {
// // when dropped, update localstorage
savePage(draggedArray);
// }
}
// get each full bus-route div in #bus-list with p content as single arr item each
// called when item is dropped
function savePage(dArray) {
// honestly i can't remember what this does precisely
// but i can't seem to add to localstorage in the way i want without it
var arr = Array.prototype.map.call(dArray, function(elem) {
return elem.outerHTML;
});
localStorage.newList = JSON.stringify(arr); // have to stringify the array in order to add it to localstorage
}
// ideally it should just update the order of the bus routes to reflect local storage
// and add classes/ids to the divs etc. (hence using outerHTML)
function makePage() {
// getting the item from localstorage
var getData = localStorage.getItem("newList");
// parsing it back into an array
var parsedData = JSON.parse(getData);
// string to hold contents of array so they can be display via innerHTML
var fullList = "";
if (localStorage.getItem("newList") === null) {
return; // maybe this is wrong but if there's nothing in local storage, don't do anything
} else {
for (let i = 0; i < parsedData.length; i++) {
fullList = fullList + parsedData[i];
}
busList.innerHTML = fullList;
// reassigning targets after calling function in order to re-register event handlers
draggable = document.querySelectorAll('[draggable]');
targets = document.querySelectorAll('[data-drop-target]');
}
}
// probably better way to do this
for (let i = 0; i < draggable.length; i++) {
draggable[i].addEventListener("dragstart", handleDragStart);
draggable[i].addEventListener("dragend", handleDragEnd);
}
// drop target elements
for (let i = 0; i < targets.length; i++) {
targets[i].addEventListener("dragover", handleOverDrop);
targets[i].addEventListener("drop", handleOverDrop);
targets[i].addEventListener("dragenter", handleDragEnterLeave);
targets[i].addEventListener("dragleave", handleDragEnterLeave);
}
// rolling average: new_average_score = old_average_score * (total_users-1)/total_users + user_route_rank/total_users
// user id, column per route (score)
// session id
// submit button to save changes to db
// when submit is clicked
// get current results from either local storage or from currently ordered + displayed list
// data will be in array format
// do i need AJAX to be able to submit something other than form data to a server?
// is submitting data in this format (taken from page's HTML ordering) even possible?
// i'd prefer to not use jQuery
// how do i submit an array to the server (express) and then parse (?) it to make it useable?
// so far i have tried JSON.stringify/parse, making a custom object, just submitting array, etc.
// ultimately what i need is a data type that i can loop over and compare positions of stored elements
var makeRouteArray = function() {
var currentOrderArr = Array.from(busList.children);
var idData = currentOrderArr.map(function(el) {
return Number(el.id.slice(2));
});
return idData; // not sure if having two return statements like this is okay
};
button.addEventListener("click", function(e) {
var request = new XMLHttpRequest();
request.open('POST', '/submit', true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.send(data);
});
makePage();
The makeRouteArray function is taking ids from an HTML ul and stripping them of the first two characters because I only want to use the numbers (the ids are structured as #r-num). An array of these numbers is what I would like to submit to the server and if possible, once on the server, return the JSON object to an array.
Thanks!

getting responseID of latest form submission in google apps

I have a google form. Every time it is submitted, the answers go into a google spreadsheet. I am trying to write an app script that triggers every time the form is submitted, and adds an "edit" link in the column to the right of the data from the form. The link itself is easy to generate, google has a method called getEditResponseURL(). (https://developers.google.com/apps-script/reference/forms/form-response)
But everytime I run it, I am getting the error "TypeError: Cannot call method "getResponses" of null."
Here is my code:
function addeditlink(e) {
// Get the active sheet
var sheet = SpreadsheetApp.getActiveSheet();
// Get the active row
var row = sheet.getActiveCell().getRowIndex();
//get the form
var form = FormApp.getActiveForm();
//get latest form response
var responses = form.getResponses();
var lastResponse = responses[responses.length - 1];
//get edit URL
var editurl = lastResponse.getEditResponseUrl();
//build link
var editlink = "Edit";
//place edit link in column R (index 18)
if (sheet.getRange(row, 18).getValue() == "") {
sheet.getRange(row, 18).setValue(editlink);
}
}
Any help? Thanks!
The solution is to remove:
var form = FormApp.getActiveForm(); //this is WRONG
and replace with:
var form = FormApp.openByID(' ID here ')
There is no "active form", because this script is being run in sheets, not forms.
I think you can only call FormApp.getActiveForm() from a script attached to a form, whereas your script is contained in a GSheet. I couldn't find a way to easily gets forms that used this sheet as its destination so what I've did was get all of the forms and then looked at the destination id of each and checked if it is the same as this spreadsheet. Once you've got your Form object you can get the responses. Feels a bit long winded would love to know if anyone knows a quicker way.
There are also a few exceptions that FormApp throws that you have to cope with.
Here's the function I use:
/**
* Find the first form that is linked to a specific spreadsheet
*
* #param {string} spreadsheet id
* #return {object} Form or null
*/
function getFormByDestinationId_(spreadsheetId) {
var formFiles = DriveApp.getFilesByType('application/vnd.google-apps.form');
var form;
var formFile;
var formId;
var destinationId;
while (formFiles.hasNext()) {
formFile = formFiles.next();
formId = formFile.getId();
// Throws an error if ID invalid
try {
form = FormApp.openById(formId);
} catch (error) {
if (error.name === "Exception") {
// Just ignore it
} else {
throw error;
}
}
// Form.getDestinationId() throws an error if there is no destination id
try {
destinationId = form.getDestinationId();
} catch (error) {
if (error.name === "Exception") {
// Just ignore it
} else {
throw error;
}
}
if (destinationId !== spreadsheetId) {
continue;
}
return form;
}
return null;
} // getFormByDestinationId_()
The only line using: getResponses() method is this one:
var responses = form.getResponses();
Your error:
Cannot call method "getResponses" of null
Means that form is null. If form is null, then this line:
//get the form
var form = FormApp.getActiveForm();
is not working. So, why isn't it working? There is nothing wrong with the code, so it must be a different problem. If there was an active form, that code would return a form type. This means that there is no form bound to the script. getActiveForm()
Returns the form to which the script is container-bound.
Your script is not "container-bound" to the form. Your script is bound to the spreadsheet.
The documentation states:
To interact with forms to which the script is not container-bound, use openById(id) or openByUrl(url) instead.
You can bind your script to the form by opening the script editor from the edit page of the form. But, there's no need to do that if you want to keep your script bound to the spreadsheet.
The line var form = FormApp.getActiveForm(); isn't going to work in your spreadsheet script.
The problem with using the Event Object e with an installable trigger, is that it looks like you can't get the response URL.
google_sheets_events
This means that you need to use openById(id) or openByUrl(url) inside the script bound to the spreadsheet, or move all your script to the form.
Here is how to get the edit url from script in the spreadsheet:
// Open a form by ID.
var form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
Now the problem is, that you can only get the Edit Response URL: getEditResponseUrl() through the "FormResponse" class. So you need the Form Responses.
var formResponses = form.getResponses();
But that's all the responses, you need the last one.
var lastResponseIndex = formResponses.length - 1;
var lastResponse = formResponses[lastResponseIndex];
var editURL = lastResponse.getEditResponseUrl();
or:
function getEditURLofLastResponse() {
// Open a form by ID.
var form = FormApp.openById('Your Form ID');
var formResponses = form.getResponses();
//get last respnse
var lastResponseIndex = formResponses.length - 1;
var lastResponse = formResponses[lastResponseIndex];
var editURL = lastResponse.getEditResponseUrl();
Logger.log(editURL);
}
Just an observation:
You are using an e argument: function addeditlink(e) {. But I don't see it being used in your code. That makes me wonder if you are using an "installable" trigger, as opposed to a "simple" trigger.
It's possible to get the values that were just submitted with e.values or e.namedValues. But you can't get the Edit URL with the Event Object.

Categories