Using the 'this' keyword in nested functions

I am facing an issue with the following code snippet :

<!DOCTYPE html>

<html>
    <head>
        <title>Drag-Drop tests</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <script>
            var body = document.body;
            
            var cursor = document.createElement("div");
            cursor.innerText = "Content of files :\n";
            
            cursor.ondragover = e => e.preventDefault();
            cursor.ondrop = function(e) {
                e.preventDefault();
                
                if (e.dataTransfer.items) {
                    for (var i = 0; i < e.dataTransfer.items.length; i++) {
                        if (e.dataTransfer.items[i].kind === "file") {
                            var file = e.dataTransfer.items[i].getAsFile();
                            
                            file.cursor = document.createElement("p");
                            body.appendChild(file.cursor);
                            
                            file.cursor.innerText = file.name + " contains :";
                            
                            file.text().then(function(value) {
                                file.cursor.innerText += " " + value;
                            });
                        }
                    }
                }
                else {
                    for(var i = 0; i < e.dataTransfer.files.length; i++) {
                        var file = e.dataTransfer.files[i];
                            
                        file.cursor = document.createElement("p");
                        body.appendChild(file.cursor);
                        
                        file.cursor.innerText = file.name + " contains :";
                        
                        file.text().then(function(value) {
                            file.cursor.innerText += " " + value;
                        });
                    }
                }
            };
            
            body.appendChild(cursor);
        </script>
    </body>
</html>

Upon dropping two files on the div element, the output I receive is as follows :

Content of files :

File1.txt contains :

File2.txt contains : Content of file 1 Content of file 2

In the 'file.text().then' function, "file" refers to the last file reference declared.

If I replace file.cursor.innerText += with this.cursor.innerText += I get the following output :

Content of files :
Content of file 1 Content of file 2

File1.txt contains :

File2.txt contains :

In the 'file.text().then' function, "this" points to the first caller which is the div itself.

Is there a way to achieve this output :

Content of files :

File1.txt contains : Content of file 1

File2.txt contains : Content of file 2

While keeping anonymous nested functions as parameters for callers.

I understand that the 'then' does not execute at the time it's defined. Can I attach data to an object and retrieve it during callback execution?

Thank you in advance.

Answer №1

When using the file.text().then function, "file" actually refers to the most recently declared file reference.

If you have a background in C programming, it's important to note that in JavaScript, variables declared with var are function scoped and undergo hoisting. It is recommended to use the newer keywords let and const for block scoped variables instead.

For more information on the differences between using "let" and "var," check out this resource.

In the context of the file.text().then function, "this" refers to the initial caller, which in this case is the div element itself.

The behavior of the keyword this in JavaScript can be tricky, especially for those who come in with preconceived notions from other languages. In JS, this is dependent on the execution context of a function/method.

To understand how the "this" keyword works in JavaScript, refer to this explanation.

As a sidenote, it seems like there is some repeated code in your snippet. Consider exploring Iterators and Generators as a way to streamline your code. These tools can help organize the data available in dataTransfer into a sequence of File objects.

var body = document.body;

var cursor = document.createElement("div");
cursor.innerText = "Contenus des fichiers :\n";

// https://mdn.io/Generator
function* getFiles(dataTransfer) {
  if (dataTransfer.items) {
    // https://mdn.io/for...of
    for (let item of dataTransfer.items) {
      if (item.kind === "file") {
        // https://mdn.io/yield
        yield item.getAsFile();
      }
    }
  } else {
    // https://mdn.io/yield*
    yield* dataTransfer.files;
  }
}

cursor.ondragover = e => e.preventDefault();
cursor.ondrop = function(e) {
  e.preventDefault();

  for (const file of getFiles(e.dataTransfer)) {
    const fileCursor = document.createElement("p");
    fileCursor.innerText = file.name + " contient :";

    body.appendChild(fileCursor);

    file.text().then(text => fileCursor.append(text));
  }
};

body.appendChild(cursor);

Edit:

I don't even see any instances of let, const, function*, yield, or yield* in the code.

It's crucial to note that in the modern development landscape as of 2020/21, the absence of const and let usage is considered outdated. These declarations were introduced with ES2015 and have been widely supported by browsers for several years.

How would you label the practice of adding an asterisk after the "function" or "yield" keyword?

The overarching concept here falls under the umbrella of Iterators and Generators. I've included specific URLs within the code snippet above to explain these different keywords in depth.

Answer №2

.text() is an example of asynchronous behavior. The .then() functions are executed only after the entire for loop has completed. This is a common issue when dealing with asynchronous operations in loops. The solution is to use the more modern approach of await instead of relying on the traditional .then() syntax.

cursor.ondrop = async function(e) {
     // ...
     for(...) {
         const value = await file.text();
         file.cursor.innerText += " " + value;
     }
}

Answer №3

JSFiddle: https://jsfiddle.net/7q9Lphjf/1/

The reason for the output you are seeing is due to the order of operations in your code. The concatenation `+= " " + value` occurs after the promise has been executed, leading to the following sequence of events:

  1. "File 1 contient" is added to the div.
  2. File 1 begins reading.
  3. "File2 contient" is added to the div.
  4. File 2 starts reading.

[...] a short time later

  1. Reading of file 1 completes, resolving the promise and appending its contents to the div.
  2. Reading of file 2 completes, resolving the promise and appending its contents to the div.

In this scenario, the issue also lies with the scope of the `file` variable being changed within the loop. By the time promise1 resolves, `file` refers to the second paragraph, causing `file.cursor.innerText` to reference the content of the second paragraph.

To address this, consider modifying your code as follows:

file.text().then(function(value) {
  file.cursor = document.createElement("p");
  body.appendChild(file.cursor);
  file.cursor.innerText = file.name + " contient :";
  file.cursor.innerText += " " + value;
});

It's important to note that this solution does not ensure a specific order of appending contents. Each file will only be added to the div once its promise is resolved (i.e., file reading is complete).

If a specific order is crucial, alternative approaches like promise chaining can be considered. However, since it's unclear if this is a requirement, I'll keep this answer concise :)

Answer №4

Alright,

One response mentioned that using "var" scopes the function and not the block.

By replacing "var" with "let" anywhere, the keyword "this" in the file.text().then callback will not point to anything due to the execution flow.

However, using "let" ensures that "file" for each iteration is independent.

As a result, I have successfully implemented this code:

<!DOCTYPE html>

<html>
    <head>
        <title>Drag-Drop tests</title>
        <meta charset="UTF-8">
    </head>
    <body>
        <script>
            let body = document.body;
            
            let cursor = document.createElement("div");
            cursor.innerText = "Contenus des fichiers :\n";
            
            cursor.ondragover = e => e.preventDefault();
            cursor.ondrop = e => {
                e.preventDefault();
                
                let fileLoad = file => {
                    file.cursor = document.createElement("p");
                    body.appendChild(file.cursor);
                    
                    file.cursor.innerText = file.name + " contient :";
                    file.text().then(value => file.cursor.innerText += " " + value);
                }
                
                if(e.dataTransfer.items)
                    for(let item of e.dataTransfer.items)
                        if(item.kind === "file") fileLoad(item.getAsFile());
                else
                    for(let file of e.dataTransfer.files)
                        fileLoad(file);
            };
            
            body.appendChild(cursor);
        </script>
    </body>
</html>

In the end, I'm not too worried about optimizing the code (in terms of line count).

I plan to generate it using some C code.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

AngularJS Formly's ui-mask feature: Stripping input mask characters from the model

I recently implemented an input mask on a 10-digit phone number field within my Formly form using AngularJS. The input is masked with dashes (xxx-xxx-xxxx) and displays correctly on the page. However, the issue arises when attempting to ignore these mask c ...

Getting values from a textbox in a checkbox using PHP

I need to collect the values from 4 checkboxes, one of them is labeled as Others and has a textbox. The goal is to retrieve all the values that the user has checked and if the user has checked the option labeled as Others, then the values from the textbox ...

Currently, I am encountering a problem as I attempt to iterate through a dynamic table

I have a table containing various elements. An example row is Jack Smith with multiple rows like this: col1 col2 col3 col4 col5 col6 col7 col8 jack smith 23 Y Y error error_code error_desc The table is ...

Guide on creating a sitemap using Express.js

I've been working with the sitemap.js package from https://www.npmjs.org/package/sitemap While I can add URLs to the sitemap manually, my challenge lies in adding URLs based on data retrieved from MongoDB. Since fetching data from MongoDB is asynchro ...

Displaying a certain div when clicked within a loop using Vue.js and Laravel

I am currently facing an issue with displaying a hidden div upon click. The problem arises when using a loop to dynamically generate all the divs. Whenever I click the button, it shows all the divs instead of just one specific div on each click. I attempte ...

What is the best way to save a reference using a POST API in NodeJs?

Currently utilizing the express.js framework for building a MERN stack application. This is my first endeavor into creating a full-stack JavaScript app, so a lot of the functions are new to me. I am in the process of sending a new post to a topic and updat ...

A step-by-step guide on constructing this JSON using PHP

Having an issue with my PHP code for sending a push notification as my JSON object is not formatting correctly... I am aiming for my JSON to be structured like this: { "to": ["xxx"], "data": { "title": "dw", "body": "dw", ...

Tips for adjusting the autocomplete maxitem dynamically

I am working on a multi autocomplete feature and I have the following code. Is there a way to dynamically set the maximum number of items based on the value entered in another text field? <script> $(function () { var url2 = "<?php echo SI ...

The issue that arises is that only the initial button is able to successfully trigger the opening of

My goal is to create a forum where users can reply to posts by clicking on a "Reply" button that triggers a Bootstrap modal. However, I am facing an issue where the modal only opens when clicking on the first button in a row due to it being placed within a ...

Retrieving Records within a Specific Date Range for Check-ins and Check-outs

Our team is currently working on developing booking software that requires handling different room pricing based on specific dates. Each room has distinct pricing for weekdays and weekends. ROOM 1 Pricing 1: September 1st - November 3rd, Weekday: $100, W ...

Vue.js Google Places Autocomplete Plugin

I'm currently working on integrating Google Places Autocomplete with Vue.js. According to the API documentation, the Autocomplete class requires an inputField:HTMLInputElement as its first parameter, like shown in their example: autocomplete = new g ...

JS and its dynamic color changes

A game has been developed using JavaScript and HTML. The game features a table with cells that are assigned IDs for toggling colors using an onClick function. The objective is simple: when a cell is clicked, all neighboring cells, including the clicked one ...

The markerwithlabel feature failed to appear on the Google Map when using MeteorJS

I am encountering an issue with markerwithlabel not displaying on Google Maps in MeteorJS. I am using dburles:google-maps and markerwithlabel v1.1.9, but I can't seem to integrate them properly. Despite placing markerwithlabel.js in the public folder, ...

Can $refs cause issues with interpolation?

I'm currently learning Vue.js and the course instructor mentioned that modifying the DOM element using $refs should not affect interpolation. In fact, any changes made directly to the DOM will be overridden by interpolation if it exists. However, in m ...

Displaying events in FullCalendar using AJAX response: A comprehensive guide

I am currently working on implementing full calendar functionality. I am trying to display events stored in a database by making an Ajax call. So far, I have successfully retrieved the events using JSON format. Here is the JSON output after using var_dump ...

Steps for displaying an image link on an aspx webpage

Is it possible to have the full-size image open on http//example.com/image.aspx when the thumbnail is clicked, instead of http//example.com/images/image.jpeg? I am looking for a solution that does not require creating individual pages for each image or edi ...

What steps should I take to retrieve the value from the daterange picker?

I am currently working on developing a user-friendly date range picker that can be used to select dates from a drop-down menu and pass them to an SQL query. At this stage, I am focused on handling the selected date and displaying it in an h1 tag. Options ...

When setting up columns in a MUI DataGrid, it's important to remember that each field must have a unique name to avoid any conflicts. Having

I am currently working on a DataGrid project where I aim to display the values of ready_by and name. Here is an image for reference: https://i.stack.imgur.com/3qZGa.png In my code configuration, the setup looks like this: (specifically focusing on the la ...

Strategies for resolving the error message "undefined" following the mapping process

Currently, I am utilizing a custom API to retrieve data. In order to accomplish this, I have structured an object that holds all the necessary data to be retrieved in a dynamic manner. My approach involves using the fetch() function to perform these data r ...

Issues with navigating jQuery UI links while offline in Google abound

Hello, I am a newcomer to jQuery and I am facing an issue with my code. I added the <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> but for some reason, my jQuery is not working. I have tried searching ...