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.
Related
Consider a node.js loop to fire off http requests, like this:
var http = require('http');
function sendOne(opts, body)
{
var post_options = {
...
}
var sender = http.request(post_options, function(res) {
// process response
});
sender.write(body)
sender.end()
return sender;
}
for( var i =0; i < maxUploadIterations; i++)
{
var body = // construct payload
var sent = sendOne(opts, body);
// somehow wait on sent...
}
Note that the http.request object has a callback function specified for handling the response. My question is how do I synchronously wait on the "Sent" object returned from sendOne using the built in Node primitives.
I understand there are multiple frameworks like Express and Futures that can handle this, but I want to understand the primitive behavior rather than using a "magic" framework.
Is it possible to take "Sent" and put a handler on it like this:
var sent = ...
sent.on("complete", function() { ... } );
?
If so, exactly which handler and how to format the loop with it?
Another option, use old fashion callbacks... Though promises may be cleaner.
var http = require('http');
function sendOne(opts, body, next) {
var post_options = {
// ...
};
var sender = http.request(post_options, function (res) {
// process response
next();
});
sender.write(body);
sender.end();
}
var maxUploadIterations = 100;
function next(index) {
// termination condition
if (index >= maxUploadIterations) {
return;
}
// setup next callback
var nextCallback = next.bind(null, index + 1);
// get these from wherever
var opts = {};
var body = {};
sendOne(opts, body, nextCallback);
}
next(0);
If you have more work to do afterwards, you can change the termination condition to call something else, rather than return.
I'm attempting to implement an asynchronous computed observable as show here.
I can do it successfully for one ajax call. The challenge I have at the moment is how to perform various ajax calls in a loop building an array asynchronously and then returning the array to my computed observable array using jQuery promises.
Basically the HTML form works in the following way:
This a student course form.
For each row, users type the person number and on another column they'll type a list of course ids separated by commas. Eg 100, 200, 300.
The purpose of the computed observable is to store an array
containing course details for the courses entered in step 2.
The details are obtained by firing ajax calls for each course and storing HTTP response in the array.
I don't want users to wait for the result, thus the reason to implement an async computed observable.
My problem: I'm having problem returning the value of the final array to the observable. It's always undefined. The ajax calls work fine but perhaps I'm still not handling the promises correctly.
Here's the code for my class:
function asyncComputed(evaluator, owner) {
var result = ko.observable(), currentDeferred;
result.inProgress = ko.observable(false); // Track whether we're waiting for a result
ko.computed(function () {
// Abort any in-flight evaluation to ensure we only notify with the latest value
if (currentDeferred) { currentDeferred.reject(); }
var evaluatorResult = evaluator.call(owner);
// Cope with both asynchronous and synchronous values
if (evaluatorResult && (typeof evaluatorResult.done == "function")) { // Async
result.inProgress(true);
currentDeferred = $.Deferred().done(function (data) {
result.inProgress(false);
result(data);
});
evaluatorResult.done(currentDeferred.resolve);
} else // Sync
result(evaluatorResult);
});
return result;
}
function personDetails(id, personNumber, courseIds) {
var self = this;
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
// Computed property to extract PIC details for additional PICs.
// This is computed observable which returns response asynchronously
self.courseDetails = asyncComputed(function () {
var courseIdsArray = self.courseIds().split(",");
var arr = [];
var arr_promises = [];
function getCourseDetails(courseId) {
var dfrd = $.Deferred();
var content = {};
content.searchString = courseId;
var url = 'MyURL';
return $.ajax(url, {
type: 'POST',
dataType: 'json',
data: requestData, // content of requestData is irrelevant. The ajax call works fine.
processdata: true,
cache: false,
async: true,
contentType: "application/json"
}).done(function (data) {
arr.push(new PicDetails(data.GenericIdentifierSearchResult[0]));
}).fail(function () {
alert("Could not retrieve PIC details");
}).then(function () {
dfrd.resolve();
});
}
if (courseIdsArray.length > 0) {
$.each(courseIdsArray, function (index, courseId) {
if (courseId.length > 0) {
arr_promises.push(getCourseDetails(courseId));
}
});
};
$.when.apply($, arr_promises).done(function () {
return arr;
})
}, this);
}
I think you dont really need a separate api/code for this.
You could just create observables for every input/value that changes on your site, and create a computed observable based on those.
e.g in rough pseudo code
self.id = ko.observable(id);
self.personNumber = ko.observable(personNumber);
self.courseIds = ko.observable(courseIds);
self.courseDetailsArray = ko.observableArray([]);
self.courseDetails = ko.computed(function() {
//computed the course details based on other observables
//whenever user types in more course ids, start loading them
$.get( yoururl, {self.courseIds and self.id}).success(data) {
when finished async loading, parse the data and push the new course details into final array
self.courseDetailsArray.push( your loaded and parsed data );
//since courseDetailsArray is observableArray, you can have further computed observables using and re-formatting it.
}
});
I have something a bit different from your approach, but you can build something like an asyncComputed out of it if you prefer:
make a simple observable that will hold the result
make a dictionary of promises that you'll basically keep in sync with the array of course ids
when the array of course ids change, add / remove from the dictionary of promises
wrap all your promises in a when (like you're doing) and set the result when they're all done
Basic idea:
var results = ko.observable([]);
var loadingPromises = {};
var watcher = ko.computed(function () {
var ids = ko.unwrap(listOfIds);
if (ids && ids.length) {
ids.forEach(function (id) {
if (!loadingPromises.hasOwnProperty(id)) {
loadingPromises[id] = $.get(url, {...id...});
}
});
var stillApplicablePromises = {};
var promises = []; // we could delete from loadingPromises but v8 optimizes delete poorly
Object.getOwnPropertyNames(loadingPromises).forEach(function (id) {
if (ids.indexOf(id) >= 0) {
stillApplicablePromises[id] = loadingPromises[id];
promises.push(loadingPromises[id]);
}
});
loadingPromises = stillApplicablePromises;
$.when.apply(this, promises).then(function () {
// process arguments here however you like, they're the responses to your promises
results(arguments);
});
} else {
loadingPromises = {};
results([]);
}
}, this);
This is the file (that may change) where you can see this "in real life": https://github.com/wikimedia/analytics-dashiki/blob/master/src/components/wikimetrics-visualizer/wikimetrics-visualizer.js
And the basic fiddle: http://jsfiddle.net/xtsekb20/1/
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);
}
}
});
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']
)
);
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;
});