Callback on successful completion of recursion - javascript

I am following Folder Drag Drop for folder upload.
function onDrop(e) {
e.preventDefault();
e.stopPropagation();
var items = e.dataTransfer.items;
for (var i=0; i<items.length; i++) {
var item = items[i].webkitGetAsEntry();
if (item) {
traverseFileTree(item, path="", function(){
//Recursion Complete (Not invoked)
});
}
}
}
var setFlag = true;
function traverseFileTree(item, path, callback) {
path = path || "";
if (item.isFile) {
item.file(function(file) {
if(setFlag)callback(null);
});
} else if (item.isDirectory) {
var dirReader = item.createReader();
dirReader.readEntries(function(entries) {
for (var i=0; i<entries.length; i++)
if(entries[i].isDirectory)setFlag = false;
for (var i=0; i<entries.length; i++) {
traverseFileTree(entries[i], path + item.name + "/",callback);
}
});
}
}
The above condition check for end of recursion does not work. Since the number of nested files and folders vary, any efficient method to check for end of recursion.

How are you supposed to do that when your traverseFileTree doesn't have a third parameter, which would be the callback?
function traverseFileTree(item, path, CALLBACK){...}
Also, that function, while not being assigned to parameter, doesn't even get called inside that function. How do you expect it to run?
You need to add some logic to indicate to your function that it has accessed all nodes. Then you call the callback.:
function traverseFileTree(item, path, callback){
...
if(allNodesAccessed) callback.call(null);
...
}

Related

How to run a function once all the ajax calls have been done

Say I have this function :
function testAjax(url) {
return $.ajax({
url: url,
type: 'GET'
});
}
Now I have a number of nodes (I am using D3), I wish to loop through. Each one may or may not have some files associated. To find out if it does I get the url on the chosen node, inspect the returned data, if it has a file/files I add it to the array of files. I then wish to log the file array only after I have gone through each node and inspected it to see if it has files.
Rest of the code is similar to this :
var allFiles = [];
nodes.each(function(d) {
testAjax(d.url)
.success(function(data) {
if (data.files) {
if (data.files.length > 0) {
for (var i = 0; i < data.files.length; i++) {
allFiles.push(data.files[i])
}
}
}
})
})
//Here is where I want to log/use (call a function passing the files array as an argument) the array of files after its been completed.
Create array of promises and use $.when() to be resolved when full array is resolved
var allFiles = [];
var promises = [];
nodes.each(function(d) {
// reference to promise
var req= testAjax(d.url)
.success(function(data) {
if (data.files) {
if (data.files.length > 0) {
for (var i = 0; i < data.files.length; i++) {
allFiles.push(data.files[i])
}
}
}
})
});
// push to array
promises.push(req);
});
$.when.apply(null, promises).then(function(){
// all promises have been resolved here
}).fail(function(){
alert('Opps ... something went wrong with one of the requests')
})
You can use a check in the success callback to see if you have reached the end of the nodes array:
var allFiles = [];
nodes.each(function(i, d) {
testAjax(d.url)
.success(function(data) {
if (data.files) {
if (data.files.length > 0) {
for (var i = 0; i < data.files.length; i++) {
allFiles.push(data.files[i])
}
}
}
// Check if the length-1 is equal to the index
if (i == nodes.length-1) doneLooping();
})
})
doneLooping() should be called as the last callback completes.
Here is a useful page on the .each() function.
Please try something like
var allFiles = [];
var totalNodes = 0,
totalSuccessResponses = 0;
nodes.each(function(d) {
totalNodes++;
testAjax(d.url)
.success(function(data) {
totalSuccessResponses++;
if (data.files) {
if (data.files.length > 0) {
for (var i = 0; i < data.files.length; i++) {
allFiles.push(data.files[i])
}
}
}
if(totalNodes == totalSuccessResponses) {
// callback function()
// is excuted after all success responses received
}
})
})
I would use $.when, to be sure all promises have been resolved before perform any treatment.
Here is an example (not directly related to you code, but using mocked calls):
var test = function (i) {
var result = $.Deferred();
setTimeout(function () {
console.log(i + ": done");
result.resolve(i);
}, 1000 + Math.floor(Math.random() * 2000));
return result;
};
// with apply to allow the possible usage of $.map on an array of objects, and $.then on each promises
$.when.apply($, [test(1), test(2), test(3)]).done(function (i1, i2, i3) {
console.log(i1 + " " + i2 + " " + i3 + " are done");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
If you want to perform post treatment on you ajax call results before invoking when you can combine the above approach with $.then and $.map

Changing <this> in object literal

I'm creating an object literal and I want to use the reserved word "this". The problem I'm having is that the "this" points to the window object in an object literal. I know the this points to the current object when used in a constructor function. Is there a way to override it so that "this" points to my object literal?
main = {
run: function()
{
var elements = [];
var allElements = document.querySelectorAll("*");
for(var i = 0; i < allElements.length; i++)
{
if(allElements[i].nodeType != 3)
{
elements.push(allElements[i]);
}
}
for(var i = 0; i < elements.length; i++)
{
// Doesn't work
// this.parseElement(elements[i]);
// Works
main.parseElement(elements[i]);
}
},
parseElement: function(e)
{
// Unimportant code
}
}
(function()
{
main.run();
})();
The thing you claim works in your question doesn't work:
var main = {
run: (function()
{
var elements = [];
var allElements = document.querySelectorAll("*");
for(var i = 0; i < allElements.length; i++)
{
if(allElements[i].nodeType != 3)
{
elements.push(allElements[i]);
}
}
for(var i = 0; i < elements.length; i++)
{
// Doesn't work
// this.parseElement(elements[i]);
// Works
main.parseElement(elements[i]);
}
})(),
parseElement: function(e)
{
// Unimportant code
}
};
<div></div>
Fundamentally, you cannot refer to the object being constructed from within the object initializer. You have to create the object first, because during the processing of the initializer, while the object does exist no reference to it is available to your code yet.
From the name run, it seems like you want run to be a method, which it isn't in your code (you've edited the question now to make it one). Just remove the ()() around the function:
var main = {
run: function() {
var elements = [];
var allElements = document.querySelectorAll("*");
for (var i = 0; i < allElements.length; i++) {
if (allElements[i].nodeType != 3) {
elements.push(allElements[i]);
}
}
for (var i = 0; i < elements.length; i++) {
this.parseElement(elements[i]);
}
},
parseElement: function(e) {
console.log("Parsing " + e.tagName);
}
};
main.run();
<div></div>
Since this is set by how the function is called for normal functions, if you want run to be bound to main so that it doesn't matter how it's called, using main instead of this is the simplest way to do that in that code.
But if you don't want to use main, you could create a bound function:
var main = {
run: function() {
var elements = [];
var allElements = document.querySelectorAll("*");
for (var i = 0; i < allElements.length; i++) {
if (allElements[i].nodeType != 3) {
elements.push(allElements[i]);
}
}
for (var i = 0; i < elements.length; i++) {
this.parseElement(elements[i]);
}
},
parseElement: function(e) {
console.log("Parsing " + e.tagName);
}
};
// Bind run
main.run = main.run.bind(main);
// Use it such that `this` would have been wrong
// if we hadn't bound it:
var f = main.run;
f();
<div></div>
Just as a side note, we can use Array.prototype.filter and Array.prototype.forEach to make that code a bit more concise:
var main = {
run: function() {
var allElements = document.querySelectorAll("*");
var elements = Array.prototype.filter.call(allElements, function(e) {
return e.nodeType != 3;
});
elements.forEach(this.parseElement, this);
},
parseElement: function(e) {
console.log("Parsing " + e.tagName);
}
};
// Use it
main.run();
<div></div>
That assumes that parseElement only ever looks at the first argument it's given (since forEach will call it with three: the entry we're visiting, its index, and the object we're looping through).

How to find out when files traverse is over?

I want to create my own drag and drop component and i use this:
Does HTML5 allow drag-drop upload of folders or a folder tree?
everything works fine, but i want to find out when files traverse is over, because i want to create an array of string, which contains file names and then do sth.
traverseFileTree function contains callbacks so there are async tasks. I need to wait until callbacks are done and i don't know how to do it. I would use jQuery.Deffered but traverseFileTree is called from a loop and -in addition- this is a recursive function.
this.dndElement.addEventListener('drop', (ev: any) => {
var items = ev.dataTransfer.items;
for (var i = 0; i < items.length; i++) {
this.traverseFileTree(items[i].webkitGetAsEntry());
}
// i want to wait until callbacks in the traverseFileTree are done and do sth with fileNames array
});
public traverseFileTree(item, path?) {
path = path || "";
if (item.isFile) {
item.file((file) => {
this.fileNames.push(file.name);
});
} else if (item.isDirectory) {
var dirReader = item.createReader();
dirReader.readEntries((entries) => {
for (var j = 0; j < entries.length; ++j) {
this.traverseFileTree(entries[j], path + item.name + "/");
}
});
}
}
UPDATE:
I did sth like this:
this.dndElement.addEventListener('drop', (ev: any) => {
var items = ev.dataTransfer.items;
for (var i = 0; i < items.length; i++) {
var item = items[i].webkitGetAsEntry();
if (item) {
this.traverseFileTree(item);
}
}
Promise.all(this.promises).then(() => { console.log(this.fileNames); });
});
public traverseFileTree(item, path?) {
path = path || "";
console.log(item);
if (item.isFile) {
this.fileNames.push(item.fullPath);
} else if (item.isDirectory) {
this.promises.push(new Promise((resolve, rejected) => {
var dirReader = item.createReader();
dirReader.readEntries((entries) => {
for (var j = 0; j < entries.length; ++j) {
this.traverseFileTree(entries[j], path + item.name + "/");
}
resolve();
})
}));
}
}
and now i get list of files ... but only from "first level".
Adir
Bdir
1File
2File
Cdir
3File
4File
5File
6File
And i get 1File,2File,5File,6File but 3File,4File don't. Why?
You kick off here :
this.traverseFileTree(items[i].webkitGetAsEntry());
And then the function public traverseFileTree(item, path?) { takes over. The function is sync (even though its recursive). Once it returns you should already be in good shape 🌹
However please note that this is webkit only :https://developer.mozilla.org/en/docs/Web/API/DirectoryReader#readEntries and is likely to be removed at some point.

Javascript Recursion returning undefined

I'm struggling in a recursive Javascript function to find a specific subdirectory. This is my code:
function navigateToParent() {
var parentFullPath = parentDirectory(); // gets the full Path String
if (parentFullPath != null) {
var parent = getDirectoryByName(parentFullPath, rootDirectory);
// set the parent directory object as the current one
currentDirectory(parent);
}
}
function getDirectoryByName(fullName, myDirectory) {
if (myDirectory.fullName == fullName) {
return myDirectory;
} else {
var subs = myDirectory.subDirectories;
for (i = 0; i < subs.length; i++) {
return getDirectoryByName(fullName,subs[i]);
}
}
}
Every directory object has the properties fullName(string),subDirectories(array of directories) and files(array of files). My aim is to get the correct directory object, where it's fullName is matching.
I know, that i have to break the for loop in some way, but i don't know how to do it exactly.
After overthinking the logic i came to this solution (seems to work):
function getDirectoryByName(fullName, myDirectory) {
if (myDirectory.fullName == fullName) {
return myDirectory;
} else {
var subs = myDirectory.subDirectories;
for (i = 0; i < subs.length; i++) {
var match = getDirectoryByName(fullName, subs[i]);
if (typeof match !== "undefined"){
return match;
}
}
}
}

Search through JSON to find sepcific values

I am trying to search a JSON from client to get specific value , linear search is working if i go with specific keys but when i search from a key in middle of json it returns nothing.
Here is what i am trying
$.each(jsonStr, function (i, v) {
var folderID = '1408614499420';
if (v['#id'] == folderID) {
alert(v.folder.file['#id']);
}
});
I want to search a specific folder by its id and get all files in that specific folder.
Here is the Fiddle with actual json.
do u want something like this
$("#menuArea li").live('click', function (event) {
$.each(jsonStr.hierarchy.folder["folder"], function (i, v) {
var folderID ='1408614499420';//any id of folder
if (v['#id'] == folderID) {
alert(v.file['#id']);//file where folder id is folderID
}
});
event.stopPropagation();
});
Here's working fiddle
http://jsfiddle.net/9kw99L2h/6/
Edit :-
Code for getting all subfolders & files
function getAllSubFolders(folder) {
var subFolders = [];
if (folder.folder) {
if (folder.folder instanceof Array) {
for (var i = 0; i < folder.folder.length; i++) {
subFolders[subFolders.length] = folder.folder[i]['#id'];
subFolders = subFolders.concat(getAllSubFolders(folder.folder[i]))
}
} else {
subFolders[subFolders.length] = folder.folder['#id'];
subFolders = subFolders.concat(getAllSubFolders(folder.folder))
}
}
return subFolders;
}
function getAllFiles(folder) {
var files = [];
if (folder.file) {
if (folder.file instanceof Array) {
for (var i = 0; i < folder.file.length; i++) {
files[files.length] = folder.file[i]['#id'];
}
} else {
files[files.length] = folder.file['#id'];
}
}
if (folder.folder) {
files = files.concat(getAllFiles(folder.folder));
}
return files;
}
Fiddle http://jsfiddle.net/9kw99L2h/9/

Categories

Resources