Phonegap: navigating through directories, index position is incorrect - javascript

I'm trying to navigate through my music directory using a click event - I can get the original listview showing up ok to begin with, the click event will drill down to the next directory, but on the third click my index position keeps returning the fullPath of the original directoryentry object.
JS
function readerSuccess(entries) {
$("#test").empty();
for (i=0;i<entries.length;i++){
$("#test").append("<li>"+entries[i].name+"</li>");
}
$("#test").listview("refresh");
ListClickHandler(entries);
}
var ListClickHandler = function(something){
$(document).on("click","#test li",function(event){
var mylistname = $(this).text();
var index = $("#test li").index(this);
var listpath = something[index].fullPath; //this is causing the problem. entries never changes.
alert(index);
if(something[index].isFile ===true)
{
$("#test").empty();
alert("this is a file");
}
else if(something[index].isDirectory===true)
{
var directoryentry = new DirectoryEntry(mylistname,listpath);
var directoryreader = directoryentry.createReader();
directoryreader.readEntries(readerSuccess,fail);
//alert("this is a directory"+mylistname+listpath);
}
});
}

I managed to get this working using the following code. Im sure its a bit of mess but hopefully will help someone out if they are stumped on the same issue. Basically this will start off by reading the first directories in my music root folder, then when each list directory item is clicked it will refresh with the subdirectories, files etc.
I had to move the $(document),on("click" .....) into the readerSuccess function so it was picking up the correct list entry when the list click event was fired. I also added two new arrays to contain entries.name and entries.fullPath which get emptied on each iteration and repopulated with the correct data. The following code is how i got it to work but im sure there is a better way out there. I couldn't find anything after googling hopelessly.
function readerSuccess(entries) {
$("#test").empty();
var listnames = new Array();
var listpositions = new Array();
for (i=0;i<entries.length;i++){
if (entries[i].isDirectory===true)
{
alert("this is a directory");
}
if (entries[i].isFile===true){
alert("this is a file")
}
$("#test").append("<li>"+entries[i].name+"</li>");
listnames.push(entries[i].name);
listpositions.push(entries[i].fullPath);
}
$("#test").listview("refresh");
$(document).on("click","#test li",function(event){
var mylistname = $(this).text();
//alert(mylistname);
var index = $("#test li").index(this);
//alert(index);
var listpath = listpositions[index];
//alert(listpath);
listnames.length=0; //resets the array so new data appends with correct pos
listpositions.length=0; //resets the array so new data appends with correct pos
var directoryentry= new DirectoryEntry(mylistname,listpath);
var directoryreader = directoryentry.createReader();
directoryreader.readEntries(readerSuccess,fail);
});

Related

Move Files in google script

i use the function MoveFiles() to copy file into other folder.
But when i ran it, i try to delete the file in the original folder. After deleted it, i saw that the file that i moved also deleted.
How to make the file that i moved not be deleted too?
Tqvm
function MoveFiles() {
var SourceFolder = DriveApp.getFolderById('1WIZxuF_r9I-510Kfw9N0AImcS1Uf63dC');
var SourceFiles = DriveApp.getFolderById('1QfFl5JIfOYaTXZyFpuBNSMzBdBrXLll9').getFiles();
var DestFolder = DriveApp.getFolderById('1_03PnkJlt6mTo5bAExUMOdZVVkzMAUsA');
while (SourceFiles.hasNext()) {
var file = SourceFiles.next();
DestFolder.addFile(file);
SourceFolder.removeFile(file);
}
}
Try switching the code line for delete and add. According to this related SO post:
I've found that I needed to reverse the last two lines (so the removeFile is done first) otherwise the removeFile actually just removes it from the folder it was just added to and not from the original parent.
I've tested it and actually get the correct result, here is my code snippet:
function myFunction() {
var folder = DriveApp.getFolderById('sourceID');
var destinationFolder = "destinationID";
var contents = folder.getFiles();
while (contents.hasNext()){
var file = contents.next();
moveFiles(file.getId(),destinationFolder);
}
}
function moveFiles(sourceFileId, targetFolderId) {
var file = DriveApp.getFileById(sourceFileId);
file.getParents().next().removeFile(file);
DriveApp.getFolderById(targetFolderId).addFile(file);
}
Hope this helps.

How can I add eventlisteners to content that has been updated from localstorage?

I am working on a web application that allows for divs to be drag and dropped/rearranged. The new order of the divs is stored in local storage and if the page is refreshed or navigated away from and then back to, the user's chosen order is then fetched from local storage and displayed to the page. So far I have most of these things working, however when the page is refreshed/navigated away from and back to, the divs are no longer able to be moved around. In the console it appears that the event listeners are no longer on the draggable targets. How can I structure/edit my code so that the divs can be rearranged even if the user has refreshed or come back to the page at a later point in time? (I think the way I am handling events currently is also not very good, so suggestions for how to improve that are more than welcome.)
// get two groups of elements, those that are draggable and those that are drop targets
var draggable = document.querySelectorAll('[draggable]');
var targets = document.querySelectorAll('[data-drop-target]');
// div immediately surrounding bus routes
var busList = document.getElementById("bus-list");
var busListBuses = busList.children;
// make array from busListBuses which is an htmlCollection (not currently using this)
var divArr = Array.from(busListBuses);
// 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
}
function handleDragEnd(e) {
e.target.classList.remove('drag-start');
}
function handleDragEnterLeave(e) {
//
}
// 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
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 restorePage() {
// 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;
}
}
// submit button to save changes to db
// 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);
}
window.addEventListener('load', function(){
restorePage();
});
I am using React with Pug, here is the Pug file in case that's helpful.
html
head
title
link(rel='stylesheet', type='text/css', href='stylesheets/normalize.css' )
link(rel='stylesheet', type='text/css', href='stylesheets/style.css' )
body
div.container
div#bus-list
for route in routeInfo
- var routeID = route.ID
- var routePos = route.position
div(class=route.type, draggable="true", data-drop-target="true", id=`r-${routePos}`).bus-route
p #{route.route} #{route.route_name}
button(type="submit", value="submit") done!
script(src='js/styling.js')
I think problem is in the sequence of your code.
JavaScript code is bening parsed as script. So browser executes commands during script loading.
How it works:
Browser loads DOM without any items
Browser loads JS and adds listeners to the elements (which aren't loaded yet)
Brower fires onLoad() and your script loads elements from the storage.
End of the cycle.
You need to add listeners after you load the elements. Not before that.
Off topic
You do some repetitive calls.
You do
var getData = localStorage.getItem("newList");
var parsedData = JSON.parse(getData);
var fullList = "";
if (localStorage.getItem("newList") === null) {
return;
}
Instead of
var getData = localStorage.getItem("newList");
if (getData === null) {
return;
}
var parsedData = JSON.parse(getData);
var fullList = "";
It hurts readability and perfomance.
I would suggest you to read some books about C languague and Code Style. That will help you on your journey.

Get subfolders from folder with multiple levels

I have code which checks all the files in subfolders for a folder. But how can I change it to not only check on subfolder level but also the subfolders of the subfolder and so on?
This is the code i have for the folder and its subfolders:
var fso = new ActiveXObject("Scripting.FileSystemObject");
fso = fso.getFolder(path);
var subfolders = new Object();
subfolders = fso.SubFolders;
var oEnumerator = new Enumerator(subfolders);
for (;!oEnumerator.atEnd(); oEnumerator.moveNext())
{
var itemsFolder = oEnumerator.item().Files;
var oEnumerator2 = new Enumerator(itemsFolder);
var clientFileName = null;
for(;!oEnumerator2.atEnd(); oEnumerator2.moveNext())
{
var item = oEnumerator2.item();
var itemName = item.Name;
var checkFile = itemName.substring(itemName.length - 3);
if(checkFile == ".ac")
{
var clientFileName = itemName;
break;
}
}
}
On each level of subfolders I need to check all the files if it can find a .ac file.
The solution I mentioned in my comment would look something like this (I don't know much about ActiveX, so there are a lot of comments so hopefully you can easily correct any mistakes):
//this is the function that looks for the file inside a folder.
//if it's not there, it looks in every sub-folder by calling itself
function getClientFileName(folder) {
//get all the files in this folder
var files = folder.Files;
//create an enumerator to check all the files
var enumerator = new Enumerator(files);
for(;!enumerator.atEnd(); enumerator.moveNext()) {
//get the file name we're about to check
var file = enumerator.item().Name;
//if the file name is too short skip this one
if (file.length<3) continue;
//check if this file's name matches, if it does, return it
if (file.substring(file.length - 3)==".ac") return file;
}
//if we finished this loop, then the file was not inside the folder
//so we check all the sub folders
var subFolders = folder.SubFolders;
//create an enumerator to check all sub folders
enumerator = new Enumerator(subFolders);
for(;!enumerator.atEnd(); enumerator.moveNext()) {
//get the sub folder we're about to check
var subFolder = enumerator.item();
//try to find the file in this sub folder
var fileName = getClientFileName(subFolder);
//if it was inside the sub folder, return it right away
if (fileName!=null) return fileName;
}
//if we get this far, we did not find the file in this folder
return null;
}
You would then call this function like so:
var theFileYouAreLookingFor = getClientFileName(theFolderYouWantToStartLookingIn);
Again, beware of the above code: I did not test it, nor do I know much about ActiveX, I merely took your code and changed it so it should look in all the sub folders.
What you want is a recursive function. Here's a simple recursive function that iterates thru each file in the provided path, and then makes a recursive call to iterate thru each of the subfolders files. For each file encountered, this function invokes a provided callback (which is where you'd do any of your processing logic).
Function:
function iterateFiles(path, recursive, actionPerFileCallback){
var fso = new ActiveXObject("Scripting.FileSystemObject");
//Get current folder
folderObj = fso.GetFolder(path);
//Iterate thru files in thisFolder
for(var fileEnum = new Enumerator(folderObj.Files); !fileEnum.atEnd(); fileEnum.moveNext()){
//Get current file
var fileObj = fso.GetFile(fileEnum.item());
//Invoke provided perFile callback and pass the current file object
actionPerFileCallback(fileObj);
}
//Recurse thru subfolders
if(recursive){
//Step into each sub folder
for(var subFolderEnum = new Enumerator(folderObj.SubFolders); !subFolderEnum.atEnd(); subFolderEnum.moveNext()){
var subFolderObj = fso.GetFolder(subFolderEnum.item());
//Make recursive call
iterateFiles(subFolderObj.Path, true, actionPerFileCallback);
}
}
};
Usage (here I'm passing in an anonymous function that get's called for each file):
iterateFiles(pathToFolder, true, function(fileObj){
Wscript.Echo("File Name: " + fileObj.Name);
};
Now.. That is a pretty basic example. Below is a more complex implementation of a similar function. In this function, we can recursively iterate thru each file as before. However, now the caller may provide a 'calling context' to the function which is in turn passed back to the callback. This can be powerful as now the caller has the ability to use previous information from it's own closure. Additionally, I provide the caller an opportunity to update the calling context at each recursive level. For my specific needs when designing this function, it was necessary to provide the option of checking to see if each callback function was successful or not. So, you'll see checks for that in this function. I also include the option to perform a callback for each folder that is encountered.
More Complex Function:
function iterateFiles(path, recursive, actionPerFileCallback, actionPerFolderCallback, useFnReturnValue, callingContext, updateContextFn){
var fso = new ActiveXObject("Scripting.FileSystemObject");
//If 'useFnReturnValue' is true, then iterateFiles() should return false IFF a callback fails.
//This function simply tests that case.
var failOnCallbackResult = function(cbResult){
return !cbResult && useFnReturnValue;
}
//Context that is passed to each callback
var context = {};
//Handle inputs
if(callingContext != null){
context.callingContext = callingContext;
}
//Get current folder
context.folderObj = fso.GetFolder(path);
//Do actionPerFolder callback if provided
if(actionPerFolderCallback != null){
var cbResult = Boolean(actionPerFolderCallback(context));
if (failOnCallbackResult(cbResult)){
return false;
}
}
//Iterate thru files in thisFolder
for(var fileEnum = new Enumerator(context.folderObj.Files); !fileEnum.atEnd(); fileEnum.moveNext()){
//Get current file
context.fileObj = fso.GetFile(fileEnum.item());
//Invoke provided perFile callback function with current context
var cbResult = Boolean(actionPerFileCallback(context));
if (failOnCallbackResult(cbResult)){
return false;
}
}
//Recurse thru subfolders
if(recursive){
//Step into sub folder
for(var subFolderEnum = new Enumerator(context.folderObj.SubFolders); !subFolderEnum.atEnd(); subFolderEnum.moveNext()){
var subFolderObj = fso.GetFolder(subFolderEnum.item());
//New calling context that will be passed into recursive call
var newCallingContext;
//Provide caller a chance to update the calling context with the new subfolder before making the recursive call
if(updateContextFn != null){
newCallingContext = updateContextFn(subFolderObj, callingContext);
}
//Make recursive call
var cbResult = iterateFiles(subFolderObj.Path, true, actionPerFileCallback, actionPerFolderCallback, useFnReturnValue, newCallingContext, updateContextFn);
if (failOnCallbackResult(cbResult)){
return false;
}
}
}
return true; //if we made it here, then all callbacks were successful
};
Usage:
//Note: The 'lib' object used below is just a utility library I'm using.
function copyFolder(fromPath, toPath, overwrite, recursive){
var actionPerFileCallback = function(context){
var destinationFolder = context.callingContext.toPath;
var destinationPath = lib.buildPath([context.callingContext.toPath, context.fileObj.Name]);
lib.createFolderIfDoesNotExist(destinationFolder);
return copyFile(context.fileObj.Path, destinationPath, context.callingContext.overwrite);
};
var actionPerFolderCallback = function(context){
var destinationFolder = context.callingContext.toPath;
return lib.createFolderIfDoesNotExist(destinationFolder);
};
var callingContext = {
fromPath : fromPath,
toPath : lib.buildPath([toPath, fso.GetFolder(fromPath).Name]), //include folder in copy
overwrite : overwrite,
recursive : recursive
};
var updateContextFn = function(currentFolderObj, previousCallingContext){
return {
fromPath : currentFolderObj.Path,
toPath : lib.buildPath([previousCallingContext.toPath, currentFolderObj.Name]),
overwrite : previousCallingContext.overwrite,
recursive : previousCallingContext.recursive
}
}
return iterateFiles(fromPath, recursive, actionPerFileCallback, null, true, callingContext, updateContextFn);
};
I know this question is old but I stumbled upon it and hopefully my answer will help someone!

Problems making GET request from jQuery

I'm trying to make an HTTP GET request using the jQuery get() function, but I'm having some trouble.
Here's what my code looks like:
// get the links on the page
var pageLinks = $.find('#pageLinks');
// loop through each of the links
$(pageLinks).find('a').each(function(){
if($(this).attr('title') !== "Next Page"){
// make a GET request to the URL of this link
$.get($(this).attr("href"), function(data) {
console.log("here");
var temp = parse_page(data);
// concatenate the return string with another
bdy = bdy+String(temp);
console.log("done");
});
}
});
There are multiple pages that I need to get data from. Since the get() function is asynchronous, I get the pages in a random order. Secondly, the concatenation does not work. Even though I get each of the pages, they're not put into bdy.
Can anyone suggest how I might deal with this?
Thanks a lot!!
Construct bdy after all pages are retrieved, i.e. store get results in a dictionary or array; wait for all gets to finish; then assemble them in the correct order.
I tried this one and it works:
// get the links on the page
var pageLinks = $('a');
var bdy
// loop through each of the links
$(pageLinks).each(function(){
console.log(this);
// make a GET request to the URL of this link
$.get($(this).attr("href"), function(data) {
// concatenate the return string with another
bdy = bdy + data.toString();
console.log(bdy);
});
});
As an example of what #muratgu has said:
var results = [];
var count = 0;
function allDone() {
var bdy = results.join("");
// do stuff with bdy
}
// get the links on the page
var pageLinks = $.find('#pageLinks');
// filter the links so we're left with the links we want
var wantedLinks = $(pageLinks).find('a').filter(function (idx) {
return $(this).attr('title') !== "Next Page";
});
// remember how many links we're working on
count = wantedLinks.length;
// loop through each of the links
wantedLinks.each(function (idx) {
// make a GET request to the URL of this link
$.get($(this).attr("href"), function (data) {
console.log("here");
var temp = parse_page(data);
results[idx] = temp;
// Decrement the count.
count--;
if (count === 0) {
// All done.
allDone();
}
});
});
You could go further and abstract this into a data type that can perform N async downloads, and then notify you when all are complete.
I just found that there are modules that allow one to manage the control flow in JS. The ones I found are:
Async
Step
For help using the above modules, see my follow up question here.

Chrome Extension with Database API interface

I want to update a div with a list of anchors that I generate from a local database in chrome. It's pretty simple stuff, but as soon as I try to add the data to the main.js file via a callback everything suddenly becomes undefined. Or the array length is set to 0. ( When it's really 18. )
Initially, I tried to install it into a new array and pass it back that way.
Is there a setting that I need to specify in the chrome manifest.json in order to allow for communication with the database API? I've checked, but all I've been able to find was 'unlimited storage'
The code is as follows:
window.main = {};
window.main.classes = {};
(function(awe){
awe.Data = function(opts){
opts = opts || new Object();
return this.init(opts);
};
awe.Data.prototype = {
init:function(opts){
var self = this;
self.modified = true;
var db = self.db = openDatabase("buddy","1.0","LocalDatabase",200000);
db.transaction(function(tx){
tx.executeSql("CREATE TABLE IF NOT EXISTS listing ( name TEXT UNIQUE, url TEXT UNIQUE)",[],function(tx,rs){
$.each(window.rr,function(index,item){
var i = "INSERT INTO listing (name,url)VALUES('"+item.name+"','"+item.url+"')";
tx.executeSql(i,[],null,null);
});
},function(tx,error){
});
});
self._load()
return this;
},
add:function(item){
var self = this;
self.modified = true;
self.db.transaction(function(tx){
tx.executeSql("INSERT INTO listing (name,url)VALUES(?,?)",[item.name,item.url],function(tx,rs){
//console.log('success',tx,rs)
},function(tx,error){
//console.log('error',error)
})
});
self._load()
},
remove:function(item){
var self = this;
self.modified = true;
self.db.transaction(function(tx){
tx.executeSql("DELETE FROM listing where name='"+item.name+"'",[],function(tx,rs){
//console.log('success',tx,rs)
},function(tx,error){
//console.log('error',tx,error);
});
});
self._load()
},
_load:function(callback){
var self = this;
if(!self.modified)
return;
self.data = new Array();
self.db.transaction(function(tx){
tx.executeSql('SELECT name,url FROM listing',[],function(tx,rs){
console.log(callback)
for(var i = 0; i<rs.rows.length;i++)
{
callback(rs.rows.item(i).name,rs.rows.item(i).url)
// var row = rs.rows.item(i)
// var n = new Object()
// n['name'] = row['name'];
// n['url'] = row['url'];
}
},function(tx,error){
//console.log('error',tx,error)
})
})
self.modified = false
},
all:function(cb){
this._load(cb)
},
toString:function(){
return 'main.Database'
}
}
})(window.main.classes);
And the code to update the list.
this.database.all(function(name,url){
console.log('name','url')
console.log(name,url)
var data = []
$.each(data,function(index,item){
try{
var node = $('<div > '+item.name + '</div>');
self.content.append(node);
node.unbind();
node.bind('click',function(evt){
var t = $(evt.target).attr('href');
chrome.tabs.create({
"url":t
},function(evt){
self._tab_index = evt.index
});
});
}catch(e){
console.log(e)
}
})
});
From looking at your code above, I notice you are executing "self._load()" at the end of each function in your API. The HTML5 SQL Database is asynchronous, you can never guarantee the result. In this case, I would assume the result will always be 0 or random because it will be a race condition.
I have done something similar in my fb-exporter extension, feel free to see how I have done it https://github.com/mohamedmansour/fb-exporter/blob/master/js/database.js
To solve a problem like this, did you check the Web Inspector and see if any errors occurs in the background page. I assume this is all in a background page eh? Try to see if any error occurs, if not, I believe your encountering a race condition. Just move the load within the callback and it should properly call the load.
Regarding your first question with the unlimited storage manifest attribute, you don't need it for this case, that shouldn't be the issue. The limit of web databases is 5MB (last I recall, it might have changed), if your using a lot of data manipulation, then you use that attribute.
Just make sure you can guarantee the this.database.all is running after the database has been initialized.

Categories

Resources