Ajax IndexedDB Delete Current Sucesfull Upload - javascript

I posted something similar yesterday but it works but only deleted the last object in the data.
What I want to happen
This ajax upload will be handling a lot of data, so I'm using indexeddb. This will also be use on mobile phones. So I wanted it to upload one item at a time and if one item failed to have only deleted the previous items out of the data so they wouldn't need to upload everything again.
I have tried async = false, This works exactly how i want it but this freezers browser.
Current Code Tried to comment out any bits that could be confusing, currently this only deletes the last item once finished.
function uploadData(e) {
//Get Database
var transaction = db.transaction(["data"], "readonly");
var objectStore = transaction.objectStore("data");
var cursor = objectStore.openCursor();
//Starts Looping
cursor.onsuccess = function(e) {
var res = e.target.result;
if (res) {
if (navigator.onLine) {
$('.popup-heading').text('Uploading...');
var passData = {
client_id: res.value.client_id,
parent_id: res.value.parent_id,
storename: res.value.storename,
image: res.value.image,
key: res.key,
};
var jsonData = JSON.stringify(passData);
$.ajax({
url: "{{ path('destination_app_ajax') }}",
type: "post",
// Works but freezes browser
/*async, flase*/
data: {
"json": passData
},
success: function(JsonData) {
//Delete item once successfull
var t = db.transaction(["data"], "readwrite");
var request = t.objectStore("data").delete(passData.key);
t.oncomplete = function(event) {
console.log('item deleted');
};
},
error: function() {
$('.popup-heading').text('Upload Failed!');
}
});
} else {
$('.popup-heading').text('Please find stronger signal or wifi connection');
}
res.
continue ();
}
}
}

It sounds like you have a scope issue with passData. Inside of your loop, but before you defined var passData = ... try wrapping the codeblock with an anonymous function:
(function() {
/* Your code here */
}());
That should prevent passData from leaking into the global scope, which seems to be why your IDB code only works on the last loop. (passData is being redefined each time before your AJAX response is able to complete.)
Update: There is no loop, you're dealing with callbacks. What I see happening is that you're redefining your onsuccess handler on each Ajax request (and overwriting all values but the last), reusing the same transaction. Try moving this transaction code into the success callback for the AJAX request:
//Get Database
var transaction = db.transaction(["data"], "readonly");
var objectStore = transaction.objectStore("data");
var cursor = objectStore.openCursor();
That will create a closure and commit your delete transaction on each response. That means one transaction per AJAX request, and one onsuccess callback per AJAX request (with no redefining).

The only solution I found worked with this was to send the Key via ajax to php then delete from that.
HTML
var passData = {
.......
key: res.key,
};
.....
$.ajax({
url: "yourscript.php",
type: "post",
data: {
"json": passData
},
success: function(JsonData) {
jsonKey = JSON.parse(JsonData);
//Delete item once successfull
var t = db.transaction(["data"], "readwrite");
var request = t.objectStore("data").delete(parseInt(jsonKey.key));
t.oncomplete = function(event) {
console.log('item deleted', jsonKey.key);
};
}
PHP
$data = $_POST['json'];
$returnKey = json_encode(
array(
'key' => $data['key']
)
);

Related

Symfony : Add data from Ajax call to js array

I have an Ajax request that fetch data in database.
These data may vary in function of the action that calls the ajax request.
Each time the Ajax request is called, I want some ot these datas to be pushed in a javascript array declared outside of the Ajax function.
The problem is, each time Ajax is called, my function manage to put the wanted data in the array but erases the previous data.
Here is the script :
<script>
let myArray = [];
function fetchWeeksForViewportWidth(startDate) {
//AJAX
$.ajax({
method: "GET",
url: "{{path('days_json')}}",
data: {
//some data
},
success: function (data) {
let trParents = document.getElementsByClassName('project-assignment');
$.each(trParents, function(key, parent) {
let assId = parent.dataset.assignmentId;
myArray[assId] = [];
$.each(data['days'], function(key, value) {
myArray[assId][value.code] = 0;
//some other treatment
if(value.code in data['game']) {
myArray[assId][value.code] = data['game'][value.code];
});
});
});
},
error: function() {
//handle error
console.log("error")
}
});
}
$(document).ready( function () {
function displayArray(){
console.log(myArray);
setTimeout(displayArray, 5000);
}
displayArray();
});
</script>
Any idea why new data don't add to the ones from previous ajax calls but keep replacing them ?
Instead of assignment
myArray[assId][value.code] = 0;
try the push() function of the array
myArray[assId].push(0);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push

How to insert Data into IndexedDB within an AJAX response?

lets say I have a the following code that creates the database OUTSIDE of my Ajax call:
// This works on all devices/browsers, and uses IndexedDBShim as a final fallback
var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.shimIndexedDB;
// Open (or create) the database
var open = indexedDB.open("MyDatabase", 1);
// Create the schema
open.onupgradeneeded = function() {
var db = open.result;
var store = db.createObjectStore("MyObjectStore", {keyPath: "id"});
var index = store.createIndex("NameIndex", ["name.last", "name.first"]);
};
and then I want to store some data into this database within my Ajax call like so:
$.ajax({
url: "https://sometise.com,
headers: {
"Authorization": "TOKEN"
},
type: "GET",
success: function (data) {
$(data).each(function (index, el) {
var myArray = {
ID: el.id,
Content: data
}
///ADDING THE ABOVE ARRAY TO THE DATABASE LIKE SO HERE
open.onsuccess = function() {
// Start a new transaction
var db = open.result;
var tx = db.transaction("MyObjectStore", "readwrite");
var store = tx.objectStore("MyObjectStore");
var index = store.index("NameIndex");
// Add some data
store.put({id: el.id, name: {first: myArray, last: "Doe"}});
// Close the db when the transaction is done
tx.oncomplete = function() {
db.close();
};
}
});
}
});
but the above code doesn't do anything!
any pointers would be appreciated.
The open.onsuccess event gets fired only once. This should happen as soon as the DB is ready, which likely happens before the ajax call receives a response from the server. So the event gets fired before you attach the listener, meaning it never gets handled.
You have to store the onsuccess result outside of the ajax callback. Or you can use a promisified version of IDB like https://github.com/jakearchibald/idb and await it inside the ajax callback.
Edit: An example using promises, await and the idb library I mentioned above:
const db = openDB(…);
async function doPost() {
const response = fetch('https://somesite.com')
const tx = (await db).transaction("MyObjectStore", "readwrite");
const store = tx.objectStore("MyObjectStore");
const index = store.index("NameIndex");
// Add some data
await store.put(await response);
await tx.done;
}
Should work like this, but I would recommend reading up about promises in general. Also consider using modern language features like const/let and fetch, they usually make live much easier.

ajax call is failing when called in a function inside a for loop

I am trying to call an API and get and display all orders and then for each order to call another endpoint which returns the details about the worker who has placed the order and display these details along with the order details.
Using Ajax, I call the first endpoint which returns all the order. I looped through all returned orders and display the details about each order. Inside the loop I call a function which take as parameter the WorkerId (getWorker(WorkerId). This function is created outside the for loop and contains another ajax call to the second API endpoint, which returns the details about the worker.
var app = {}; // creating a empty object to hold the data
// getAllOrders method will make the Ajax request to the API
app.getAllOrders = function() {
$.ajax({
url: 'https://www.hatchways.io/api/assessment/work_orders',
method: 'GET',
dataType: 'json',
data: {
format: 'json'
},
success: function(result) {
console.log('Ajax is working.');
app.displayAllOrders(result);
},
error: function(error) {
console.log('Something went wrong.');
console.log(error);
}
});
}; //end app.getAllOrders function
app.getAllOrders();
app.displayAllOrders = function(allOrders) {
console.log(allOrders);
// getWorker method will make the Ajax request to the API
app.getWorker = function(id) {
console.log('Ajax is working.');
$.ajax({
url: 'https://www.hatchways.io/api/assessment/workers/' + id,
method: 'GET',
dataType: 'json',
data: {
format: 'json'
},
success: function(result) {
console.log('Ajax is working.');
app.workersInfo(result);
},
error: function(error) {
console.log('Something went wrong.');
console.log(error);
}
});
}; //end app.getWorker function
// creating a method to inject our data into the DOM
app.workersInfo = function(worker) {
console.log(worker);
// Creating var for each piece of data for worker id
var comp = $('<p>').text(worker.worker.companyName);
var nameWorker = $('<p>').text(worker.worker.name);
var email = $('<p>').text(worker.worker.email);
var workerId = $('<p>').text(worker.worker.id);
var workerInfo = $('<div>').append(nameWorker, comp, email, workerId);
$('#allOrders').append(workerInfo);
}; //end app.workersInfo function
for (var i = 0; i < allOrders.orders.length; i++) {
// creating var for each piece of data for order
var id = $('<p>').text(allOrders.orders[i].id);
var nameOrder = $('<h3>').text(allOrders.orders[i].name);
var desc = $('<p>').text(allOrders.orders[i].description);
var deadline = $('<p>').text(allOrders.orders[i].deadline);
var workerId = $('<p>').text(allOrders.orders[i].workerId);
// appending in div all info for the order
var orderInfo = $('<div>').addClass('orderInfo').append(nameOrder, id, desc, deadline, workerId);
// appending in div all info for worker
$('#allOrders').append(orderInfo);
// call getWorker function to display worker
var myWorkerId = allOrders.orders[i].workerId;
app.getWorker(myWorkerId);
};
};
When getWorker(myWorkerId) is called, the Ajax call inside it is not called. This is called only after iterating through the loop is done. I tried with another function inside getWorker() instead of Ajax call and this is working. Please let me know if you can spot what I am doing wrong and how this can be fixed.

"Cannot call method 'transaction' of undefined" error in indexedDB

Below is my code. It runs fine for the first time but once I refresh the page it generates an uncaught TypeError: Cannot call method 'transaction' of undefined.
Basically I retrieve from data from a database and encode it into a usable JSON datatype to avoid insert issues into my tables.
$(document).ready( function() {
//prefixes of implementation that we want to test
window.indexedDB = window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB;
//prefixes of window.IDB objects
window.IDBTransaction = window.IDBTransaction ||
window.webkitIDBTransaction ||
window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange ||
window.webkitIDBKeyRange ||
window.msIDBKeyRange
if(!window.indexedDB){ //alert user if browser does not support
window.alert("Your browser doesn't support a stable version of IndexedDB.")
}
var request = window.indexedDB.open("runningClub", 1); //create a runningClub database
var browserDB;
request.onsuccess = function(event){
browserDB = request.result;
};
var raceIDs = [];
$(".raceID").each( function () {
raceIDs.push(" raceID = " + $(this).val());
});
$.ajax({
url: 'controller/AJAX/userAction.php',
type: 'post',
dataType: 'json',
data: { statement : "getRaceResult", raceIDs : raceIDs },
success: function(data) {
const results = data;
request.onupgradeneeded = function(event){
var browserDB = event.target.result;
var objectStore = browserDB.createObjectStore("raceResult", {keyPath: "resultID"});
for(var i in results){
objectStore.add(results[i]);
}
}
var objectStore = browserDB.transaction("raceResult").objectStore("raceResult");
objectStore.openCursor().onsuccess = function(event){
var cursor = event.target.result;
if (cursor){
$("#memberName").append("<option>" + cursor.value.memberName + "</option>");
cursor.continue();
}
};
}
});
});
Thanks for posting the (almost) entirety of your code. Unfortunately I can't fully replicate your issue without knowing the structure of the data object coming from your backend. Mind publishing that as well?
That said, I'm rather certain your issue is the use of const.
const results = data;
I do not believe this type of data to be "structured clone"-able. Given that JSON is fully clonable, which is what I imagine to be returned from your API (vs. e.g. JavaScript that could be evaulated to produce a function), the usage of this keyword seems the likely culprit of your issues.
Update 1 Working through this, quite a few issues with your async programming.
database is opened before ajax request, rather than after successful callback. seems prudent to move this into the callback.
browserDB is defined in a success callback that isn't guaranteed to run before the async operation completes (thus browserDB becomes undefined)
request.onsuccess is called twice (you may not have realized this, because it's weird behavior): both when no upgradeneeded event fires and when an upgradeneeded event fires, so you're redefining browserDB perhaps accidentally when the DB doesn't yet exist
Update 2 Here's a mostly working, simplified Fiddle. I'm sad at how much time I spent making your code work.
More issues (beyond const):
hardcoded database version
reusing versionchange event for adding/getting data is a bad idea
need to use a readwrite transaction to add data
openCursor - but don't supply parameters for resultID keypath
you are not listening for successful adds before getting the data
you are not chaining your success events for adds before getting the data
The current loop approach was untenable. But I got it working with a single add and a cursor get. You can refactor back into a multiple-add approach but you'll need to watch your async (based on what I see, I'd suggest you stick with simple).
$.ajax({
url: '/echo/json/',
type: 'post',
dataType: 'json',
data: {
json: JSON.stringify({
resultID: new Date().getTime(),
raceID: "1",
memberID: "824",
runtime: "00:31:26",
memberName‌: "Mark Hudspith"
})
},
success: function (data) {
var request,
upgrade = false,
doTx = function (db, entry) {
addData(db, entry, function () {
getData(db);
});
},
getData = function (db) {
db.transaction(["raceResult"], "readonly").objectStore("raceResult").openCursor(IDBKeyRange.lowerBound("0")).onsuccess = function (event) {
var cursor = event.target.result;
if (null !== cursor) {
console.log("YOUR DATA IS HERE", cursor.value.memberName‌);
cursor.continue();
}
};
},
addData = function (db, entry, finished) {
var tx = db.transaction(["raceResult"], "readwrite");
tx.addEventListener('complete', function (e) {
finished();
});
tx.objectStore("raceResult").add(entry);
};
request = window.indexedDB.open("runningClub");
request.onsuccess = function (event) {
if (!upgrade) {
doTx(request.result, data);
}
};
request.onupgradeneeded = function (event) {
var db = event.target.result;
db.createObjectStore("raceResult", {
keyPath: "resultID"
});
setTimeout(function () {
doTx(db, data);
}, 5000);
}
}
});

Accessing outer scope

I'm working on creating a Users collection with the ability to then grab single users inside. This will be used to match from another system, so my desire is to load the users once, and then be able to fine/match later. However, I'm having a problem accessing the outer users collection from an inner method.
function Users(){
var allUsers;
this.getUsers = function () {
// ajax to that Jasmine behaves
$.ajax({
url: '../app/data/jira_users.json',
async: false,
dataType: 'json',
success: function(data) {
allUsers = data;
}
});
return allUsers;
};
this.SingleUser = function (name) {
var rate = 0.0;
var position;
this.getRate = function () {
if(position === undefined){
console.log('>>info: getting user position to then find rate');
this.getPosition();
}
$.ajax({
url: '../app/data/rates.json',
async: false,
dataType: 'json',
success: function(data) {
rate = data[position];
}
});
return rate;
};
this.getPosition = function () {
console.log(allUsers);
//position = allUsers[name];
return position;
};
//set name prop for use later I guess.
this.name = name;
};
}
and the test that's starting all of this:
it("get single user's position", function(){
var users = new Users();
var someone = new users.SingleUser('bgrimes');
var position = someone.getPosition();
expect(position).not.toBeUndefined();
expect(position).toEqual('mgr');
});
The getPosition method is the issue (which might be obvious) as allUsers is always undefined. What I have here is yet another attempt, I've tried a few ways. I think the problem is how the Users.getUsers is being called to start with, but I'm also unsure if I'm using the outer and inner vars is correct.
Though the others are correct in that this won't work as you have it typed out, I see the use case is a jasmine test case. So, there is a way to make your test succeed. And by doing something like the following you remove the need to actually be running any kind of server to do your test.
var dataThatYouWouldExpectFromServer = {
bgrimes: {
username: 'bgrimes',
show: 'chuck',
position: 'mgr'
}
};
it("get single user's position", function(){
var users = new Users();
spyOn($, 'ajax').andCallFake(function (ajaxOptions) {
ajaxOptions.success(dataThatYouWouldExpectFromServer);
});
users.getUsers();
var someone = new users.SingleUser('bgrimes');
var position = someone.getPosition();
expect(position).not.toBeUndefined();
expect(position).toEqual('mgr');
});
This will make the ajax call return whatever it is that you want it to return, which also allows you to mock out tests for failures, unexpected data, etc. You can set 'dataThatYouWouldExpectFromServer' to anything you want at any time.. which can help with cases where you want to test out a few different results but don't want a JSON file for each result.
Sorta-edit - this would fix the test case, but probably not the code. My recommendation is that any time you rely on an ajax call return, make sure the method you are calling has a 'callback' argument. For example:
var users = new Users();
users.getUsers(function () {
//continue doing stuff
});
You can nest them, or you can (preferably) create the callbacks and then use them as arguments for eachother.
var users = new Users(), currentUser;
var showUserRate = function () {
//show his rate
//this won't require a callback because we know it's loaded.
var rate = currentUser.getRate();
}
var usersLoaded = function () {
//going to load up the user 'bgrimes'
currentUser = new users.SingleUser('bgrimes');
currentUser.getRate(showUserRate);
}
users.getUsers(usersLoaded);
your approach to fill the data in allUsers is flawed
the ajax call in jquery is async so every call to users.getAllUsers would be returned with nothing and when later the success function of the jquery ajax is called then allUsers would get filled
this.getUsers() won't work. Its returning of allUsers is independent from the ajax request that fetches the data, because, well, the ajax is asynchronous. Same with getRate().
You'll have to use a callback approach, where you call getUsers() with a callback reference, and when the ajax request completes, it passes the data to the callback function.
Something like:
this.getUsers = function (callback) {
// ajax to that Jasmine behaves
$.ajax({
url: '../app/data/jira_users.json',
async: false,
dataType: 'json',
success: function(data) {
callback(data);
}
});
};
And the call would be along the lines of:
var user_data = null;
Users.getUsers(function(data) {
user_data = data;
});

Categories