0

I have this code :

<!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 = "Contenus des fichiers :\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 + " contient :";
                            
                            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 + " contient :";
                        
                        file.text().then(function(value) {
                            file.cursor.innerText += " " + value;
                        });
                    }
                }
            };
            
            body.appendChild(cursor);
        </script>
    </body>
</html>

As is, if I drop two files on the div element, I get this output :

Contenus des fichiers :

File1.txt contient :

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

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

If I replace file.cursor.innerText += by this.cursor.innerText += I get this output :

Contenus des fichiers :
Content of file 1 Content of file 2

File1.txt contient :

File2.txt contient :

In file.text().then function, "this" refers to the first caller, that is, the div itself.

Is there any way to get this :

Contenus des fichiers :

File1.txt contient : Content of file 1

File2.txt contient : Content of file 2

Keeping anonymous nested functions as param for callers.

I know that then does not occurs at the time I define it's callback. I would like to know if I can attach some data to an object and retrieve it on a callback execution.

Thx by advance.

Lewis Anesa
  • 72
  • 1
  • 12
  • 1
    Your title mentions "this" keyword. Maybe I've not had enough coffee yet, but I don't see you using `this` anywhere in the code. Instead, I see you using `file`, which is definitely not `this`. – crashmstr Mar 23 '21 at 12:31
  • The initial code was using "this", but look, I mention the file.cursor.innerText += by this.cursor.innerText += replacement. – Lewis Anesa Mar 23 '21 at 20:04

4 Answers4

1

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

You come from a C background. In JS vars are function scoped and get hoisted. Better use the newer keywords let and const for block scoped variables.

What's the difference between using “let” and “var”?

In file.text().then function, "this" refers to the first caller, that is, the div itself.

this in JS is notorious; especially for people already having expectations on how it should behave. In JS this is context-sensitive and depends on how you call a function/method.

How does the “this” keyword work?

Sidenote: I see you wrote some code twice, take a look at Iterators and Generators. I use them to "normalize" whatever is available in dataTransfer into a sequence of Files.

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 seen any let, const, function*, yeld and yeld*.

In 2020/21 that's basically a fail on the side of the course. const and let have been introduced with ES2015 (Javascript version) and are implemented in basically every browser for years.

How would you name such a subtility that consists of adding an asterisk after "fonction" or "yeld" keyword?

The general topic is Iterators and Generators. I've added some specific URLs in the code snippet above to the different keywords.

Thomas
  • 8,708
  • 1
  • 10
  • 21
  • "You come from a C background" <= exactly exact!!! I just finished 20 hours course and reading your answer I feel like I didn't learned anything on this language. I found the subtilities you told about very interesting and their understanding mandatory. – Lewis Anesa Mar 23 '21 at 20:19
  • I don't even seen any let, const, function*, yeld and yeld*. How would you name such a subtility that consists of adding an asterisk after "fonction" or "yeld" keyword? – Lewis Anesa Mar 23 '21 at 21:08
  • @LewisAnesa I've updated the answer. Hoisting and the peculiarities of `this` are some of the most common problems people have coming from other languages. – Thomas Mar 24 '21 at 07:03
  • Course date is 2016 so yes, there are serious deficiencies in the course. As always, knowing the subtleties of a language makes the differances. Thanks for all and see you soon. – Lewis Anesa Mar 24 '21 at 07:57
0

.text() is asynchronous. All the .then() occur after the whole for loop has ended. Very classic asynchronous-operation-in-a-loop problem. You just need to await instead of using the (old school) .then() syntax.

cursor.ondrop = async function(e) {
     // ...
     for(...) {
         const value = await file.text();
         file.cursor.innerText += " " + value;
     }
}
Jeremy Thille
  • 21,780
  • 7
  • 36
  • 54
0

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

You are getting this result because += " " + value happens after the promise is executed. In other words, in your example code the following steps happen:

  1. Add "File 1 contient" to the div.
  2. Start reading file 1
  3. Add "File2 contient" to the div.
  4. Start reading file 2

[...] a few moments later

  1. file1 reading is complete, so the promise is resolved and the contents of file1 are appended to the div
  2. file2 reading is complete, so the promise is resolved and the contents of file2 are appended to the div

In this particular case it also has to do with the scope of the file variable which is changed in the for loop, so by the time promise1 is resolved, file already references the second paragraph, so file.cursor.innerText refers to the second paragraph's contents.

Instead of :

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

Use:

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

This solution will however not guarantee any particular order. Whenever the promise is resolved for a file (file reading is complete), only then the contents of that file be added to the div.

If you absolutely need a specific order in which the contents are appended, there are a number of possible solutions to also achieve that, for example promise chaining.

However I'm not sure if this is a requirement, so I'll not make this answer longer than it already is :)

Darius
  • 41
  • 1
  • 5
  • Really interesting but then, no need to attach cursor to file anymore. The idea was to attach a new element on file and references it's member, asynchronously, later. Is this possible? – Lewis Anesa Mar 23 '21 at 15:20
  • Yes, that is also possible if you declare an array of objects outside of all the loops, something like `var cursors = [{}]` and then do `cursors.push({identifier: some-identifier, cursor: document.createElement("p")});` A solution based on this idea (using an array) would also offer the advantage of being able to display the files in the same order that you wish. – Darius Mar 23 '21 at 15:57
  • You can also use a simple array (no objects, just `cursors.push(document.createElement("p");` Or you could use an object instead of an array, something like `cursors[file-identifier] = document.createElement("p")` – Darius Mar 23 '21 at 16:02
  • and `file-identifier` would be a string that you create based on the file name or something like that. – Darius Mar 23 '21 at 16:03
  • Ok but none of these implies to use "this" to refer the caller. I'm realising, this wold refer to the promise, so why, when I use this.cursor.innerText it targets the div? – Lewis Anesa Mar 23 '21 at 20:02
  • Because you have declared `var cursor = document.createElement("div");` and you are probably using `this` in a scope where it means the global script. For clarity the div should be declared as `var divElement = document.createElement('div');` – Darius Mar 23 '21 at 22:09
  • @LewisAnesa Here is a JSFiddle that may help clarify the scope of this when using promises: https://jsfiddle.net/DariusM/ke2rh51d/31/ Have a look at `f.p();` and what does `this` reference in there. It's the object that contains the `Promise`, which in your case is the window object, the same one that has the `div` element declared. – Darius Mar 23 '21 at 22:38
0

Okay,

Another answer pointed out that "var" scopes the function and not the block.

Replacing "var" by "let" anywhere makes the keyword "this" in file.text().then callback not point anything beacuse of the execution flow.

Anyway, "let" makes "file" of each for iteration independant.

Therefore I have this code working :

<!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>

And at the end, I don't really care of code optimisation (for the amount of line code).

I will generate it with some C code.

Thomas
  • 8,708
  • 1
  • 10
  • 21
Lewis Anesa
  • 72
  • 1
  • 12