Safari - "TransactionInactiveError" using Facebook IndexedDB Polyfill - javascript

We are using the Facebook IndexedDB Polyfill to allow the IndexedDB API to be utilised in Safari/ Mobile Safari. However, we are experiencing a "TransactionInactiveError" when attempting to update records - the error originates from line 1567 of the Polyfill.js file: if (!me._active) throw new util.error("TransactionInactiveError");
Here's a quick example I've put together. Simply add the Facebook Polyfill script tag reference and run in Safari:
var db;
var request = indexedDB.open("polyfillTest");
request.onupgradeneeded = function () {
// The database did not previously exist, so create object stores and indexes.
db = request.result;
var store = db.createObjectStore("books", {keyPath: "isbn"});
var titleIndex = store.createIndex("by_title", "title", {unique: true});
var authorIndex = store.createIndex("by_author", "author");
// Populate with initial data.
store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});
updateItem(store);
};
request.onsuccess = function () {
db = request.result;
};
function updateItem(store) {
var request = store.get(123456);
request.onsuccess = function () {
var book = request.result;
book.title = "New Title";
book.author = "New Author";
var updateRequest = store.put(book);
updateRequest.onsuccess = function (evt) {
console.log("Book updated successfully.");
};
updateRequest.onerror = function (evt) {
console.error("Book could not be updated.");
};
};
}
Any help appreciated!
Many thanks

Transactions are typically kept active until the last callback with a reference releases that reference. So this suggests to me your transaction is auto-commiting.
I suspect it may have something to do with your re-use of the versionchange transaction for puts and gets. After many headaches with this issue, in my library I've opted for a model where I allow all version change transactions to fully commit before trying to do CRUD operations on the same store.
I'm not fully able to explain why, but based on many days of frustration, keeping long-lived versionchanges seems to be a bad idea.

Related

Failed to execute 'put' on 'IDBObjectStore': The transaction has finished

I am trying to update an entry in my simple to do app with indexedDB, however I am getting Failed to execute 'put' on 'IDBObjectStore': The transaction has finished.
I can't seem to figure out why it won't finish the transaction, I tried the debugger and it stops at this line: var updateNameRequest = tasksStore.put( requestForItem.result.name, Number(requestForItem.result.id)) Please see the snippet I included below. For additional context creating, reading, and deleting work just fine it's just updating data that I'm having trouble with
I also tried to implement the openCursor technique which I got from Mozilla which I commented out since it also doesn't work (I get the same behavior) Check out my repo I know it's still very messy :(
const request = window.indexedDB.open("toDoList", 2);
var db;
request.onsuccess = function (event) {
console.log("check out some data about our opened db: ", request.result);
db = event.target.result; // result of opening the indexedDB instance "toDoList"
getTasks(); //just a function to retrieve data
};
$(document).on("click", ".editBtn", function () {
var transaction = db.transaction("tasks", "readwrite");
var tasksStore = transaction.objectStore("tasks");
console.log(tasksStore);
let taskId = $(this).attr("idNo");
var requestForItem = tasksStore.get(Number(taskId));
requestForItem.onsuccess = function () {
// console.log(requestForItem.result)
var oldData = requestForItem.result;
// prepopulate the input
$(".editInput").val(requestForItem.result.name);
$(".saveBtn").click(function () {
requestForItem.result.name = $(".editInput").val().trim()
console.log( requestForItem.result)
var updateNameRequest = tasksStore.put( requestForItem.result.name, Number(requestForItem.result.id))
console.log("-------------", updateNameRequest.transaction) // doesn't get to this line
updateNameRequest.onerror = function() {
console.log("something went wrong")
console.log(updateNameRequest.error)
};
updateNameRequest.onsuccess = function() {
console.log("here")
$(".editInput").val("")
getTasks();
};
});
};
Indexed DB transactions auto-commit when all requests have been completed and no further requests were made before control returns to the event loop. Put another way - you can make new requests in the success or error callback from a previous request, but not in other asynchronous callbacks such as event handlers.
You need to start a new transaction within the click handler, because any previous transaction will have autocommitted.

CouchDB put data in correct database

I've got CouchDB setup with Couchperuser. Locally I use PouchDB.
I'm building a mobile application with Cordova. It's about a todo list. with login, so users have there own list.
When I create a new user it automatically makes a new database for this user.
now, when that user is logged in and adds new todo's to his list, they go in the main database because I use :
var db = new PouchDB('http://localhost:5984/main', {skipSetup: true});
What I want to achieve is that the todo created by the specific user goes into his database. for example:
var db = new PouchDB('http://localhost:5984/userdb-41646d696e32', {skipSetup: true});
How can I automatically do this? so the var db = the users database?
I've looked around the internet but could not find anything about this.
Hope someone can help me with this scenario.
EDIT:
I'll add the code that I use for adding the todo:
var db = new PouchDB('http://localhost:5984/main', {skipSetup: true});
function addToDoItem() {
//get info
var toDoTitle = document.getElementById('toDoTitle').value;
var toDoDescr = document.getElementById('toDoDesc').value;
var addItem = {
_id: new Date().toISOString(),
title: ToDotitle,
description: ToDoDescr
};
db.put(addItem ).then(function (result){
console.log("Added to the database");
console.log(result);
}).catch(function (err){
console.log("someting bad happened");
console.log(err);
});
}
Find out, this is the way to fix it. thought of it already but seemed a bit of a cheap solution. anyway, this is the only code example I found on the internet.
dbs.remote.private = pouchDB(DATABASE.URL + "userdb-" + _convertToHex(username), {
auth: {
username: username,
password: password
}
});

Load data into local storage once?

I am using backbone.js I need a very simple way to render a local json file into the users local storage only one time. I am building a cordova app and I just want to work with local storage data.
I have hard coded a decent size .json file (list of players) into my collection, and I just want to load the .json file into the local storage if local storage on that device is empty which will only be once, upon initialization of the app.
I could use ajax, but I don't know how to write it to only inject data one time as "starter" data. So if you know how to do this I can upload the json file to my server and somehow fetch it.
I can inject the data if I go through a series of tasks, I have to disable the fetch method and render this code below in an each statement, plus the json has to be hardcoded into the collection, with a certain format.
playersCollection.create({
name: player.get('name'),
team: player.get('team'),
team_id: player.get('team_id'),
number: player.get('number'),
points: player.get('points')
})
I am trying to finish this lol I need to use it tonight to keep stats, I am almost there the structure works, when data is loaded I can add stats etc, but I need to get that data loaded, I pray someone can help!
Edit: I was able to put together some sloppy code last minuet that at least worked, thanks to #VLS I will have a much better solution, but Ill post the bad code I used.
// I fire renderData method on click
events: {
'click .renderData':'renderData'
},
// Inside my render method I check if "players-backbone" is in local storage
render: function() {
var self = this;
if (localStorage.getItem("players-backbone") === null) {
alert('yup null');
//playersCollection.fetch();
this.$el.append('<button class="renderData">Dont click</button>')
} else {
alert('isnt null');
this.$el.find('.renderData').remove();
playersCollection.fetch();
}
this.teams.each(function(team) {
var teamView = new TeamView({ model: team });
var teamHtml = teamView.render().el;
console.log($(''))
var teamPlayers = this.players.where({team_id: team.get('id')})
_.each(teamPlayers, function(player) {
var playerView = new PlayerView({ model: player });
var playerHtml = playerView.render().el;
$(teamHtml).append(playerHtml);
}, this);
this.$el.append(teamHtml);
}, this);
return this;
},
// method that populates local storage and fires when you click a button with the class .renderData
renderData: function() {
var self = this;
this.teams.each(function(team) {
var teamPlayers = this.players.where({team_id: team.get('id')})
_.each(teamPlayers, function(player) {
playersCollection.create({
name: player.get('name'),
team: player.get('team'),
team_id: player.get('team_id'),
number: player.get('number'),
points: player.get('points')
})
}, this);
}, this);
playersCollection.fetch();
return this;
}
This is obviously not the best way to go about it, but it worked and I was in such a hurry. The caveats are you have to click a button that populates the data, the collection is hard coded in, it's just overall not very elegant (but it works) the app did what it needed.
So big thanks to #VLS, I appreciate the effort to explain your code, and create a fiddle. Sorry I was so late!
You can extend your collection's fetch method and use it in conjunction with Backbone.localStorage, so inside your collection you'd have something like:
localStorage: new Backbone.LocalStorage("TestCollection"),
fetch: function(options) {
// check if localStorage for this collection exists
if(!localStorage.getItem("TestCollection")) {
var self = this;
// fetch from server once
$.ajax({
url: 'collection.json'
}).done(function(response) {
$.each(response.items, function(i, item) {
self.create(item); // saves model to local storage
});
});
} else {
// call original fetch method
return Backbone.Collection.prototype.fetch.call(this, options);
}
}
Demo: http://jsfiddle.net/5nz8p/
More on Backbone.localStorage: https://github.com/jeromegn/Backbone.localStorage

How to create multiple object stores in IndexedDB

I don't know if I'm right or wrong. But as I know I can't create a version change transaction manually. The only way to invoke this is by changing the version number when opening the indexed DB connection. If this is correct, in example1 and example2 new objectStore will never be created?
Example1
function createObjectStore(name){
var request2 = indexedDB.open("existingDB");
request2.onupgradeneeded = function() {
var db = request2.result;
var store = db.createObjectStore(name);
};
}
Example2
function createObjectStore(name){
var request2 = indexedDB.open("existingDB");
request2.onsuccess = function() {
var db = request2.result;
var store = db.createObjectStore(name);
};
}
Example3 - This should work:
function createObjectStore(name){
var request2 = indexedDB.open("existingDB", 2);
request2.onupgradeneeded = function() {
var db = request2.result;
var store = db.createObjectStore(name);
};
}
If I want to create multiple objectStore's in one database how can I get/fetch database version before opening the database??
So is there a way to automate this process of getting database version number??
Is there any other way to create objectStore other than that using onupgradeneeded event handler.
Please help. Thanks a lot.
Edit:
Here is same problem that I have:
https://groups.google.com/a/chromium.org/forum/#!topic/chromium-html5/0rfvwVdSlAs
You need to open the database to check it's current version and open it again with version + 1 to trigger the upgrade.
Here is the sample code:
function CreateObjectStore(dbName, storeName) {
var request = indexedDB.open(dbName);
request.onsuccess = function (e){
var database = e.target.result;
var version = parseInt(database.version);
database.close();
var secondRequest = indexedDB.open(dbName, version+1);
secondRequest.onupgradeneeded = function (e) {
var database = e.target.result;
var objectStore = database.createObjectStore(storeName, {
keyPath: 'id'
});
};
secondRequest.onsuccess = function (e) {
e.target.result.close();
}
}
}
The only way you can create an object store is in the onupgradeneeded event. You need a version_change transaction to be able to change the schema. And the only way of getting a version_change transaction is through a onupgradeneeded event.
The only way to trigger the onupgradeneeded event is by opening the database in a higher version than the current version of the database. The best way to do this is keeping a constant with the current version of the database you need to work with. Every time you need to change the schema of the database you increase this number. Then in the onupgradeneeded event, you can retrieve the current version of the database. With this, you can decide which upgrade path you need to follow to get to the latest database schema.
I hope this answers your question.

IndexedDB DOM IDBDatabase Exception 11 even after using oncomplete

I am very new to IndexedDB Concepts. I am trying to Store a list of movies in the IndexedDB and retrieve it. But for some reason when i try to retrieve it there is a DOM IDBDatabase Exception 11 in chrome browser. I try to retrieve it by using a simple alert. I also tried to retrieve the data by putting the alert inside an onComplete event, but this too seems to be a failure. Could someone please let me know what wrong i am doing. Below is my code.
const dbName = "movies";
var request = indexedDB.open(dbName, 1);
request.onerror = function(event) {
alert("Seems like there is a kryptonite nearby.... Please Check back later");
};
request.onsuccess = function(event) {
var db = event.target.result;
var transaction = db.transaction(["movies"],"readwrite");
var objectStore = transaction.objectStore("movies");
var request1 = objectStore.get("1");
request1.result.oncomplete=function(){
alert("The movie is"+request1.result.name);//This is the place where i get the error
}
};
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore("movies", { keyPath: "movieid" });
objectStore.createIndex("name", "name", { unique: false });
objectStore.createIndex("runtime", "runtime", { unique: false });
for (var i in movieDataToStore) {
objectStore.add(movieDataToStore[i]);
}};
I still do not know what was wrong with the last program. i re-wrote the above program and it worked like a charm. here is the code. Hope this helps anyone who is stuck with this problem. Also if anyone figures out what went wrong the last time please share your thoughts.
var db; //database will be stored in this value when success is called
var movieDataToStore = [{ movieid: "1", name: "Keep my Storage Local", runtime:"60"},
{ movieid: "2", name: "Rich Internet Conversations", runtime:"45"},
{ movieid: "3", name: "Applications of the Rich and Famous", runtime:"30"},
{ movieid: "4", name: "All Jump All eXtreme", runtime:"45"}];
window.query = function() {
db.transaction("movies").objectStore("movies").get("1").onsuccess = function(event) {
alert("QUERY: CThe first movie is" + event.target.result.name);
};};
window.onload = function() {
if (!window.indexedDB) {
window.alert("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.")
}
else{
var request = indexedDB.open("movies", 1);
request.onerror = function(event) {
alert("Seems like there is a kryptonite nearby.... Please Check back later");
};
request.onsuccess = function(event) {
db = this.result;
query();
};
request.onupgradeneeded = function(event) {
var db = event.target.result;
if(db.objectStoreNames.contains("movies")) {
db.deleteObjectStore("movies");
}
var objectStore = db.createObjectStore("movies", { keyPath: "movieid"});
objectStore.createIndex("name", "name", { unique: false });
objectStore.createIndex("runtime", "runtime", { unique: false });
for (var i in movieDataToStore) {
objectStore.add(movieDataToStore[i]);
}
};
}
};
I think it is bad practice to insert data in the onupgradeneeded context. You should be doing this separately in an unrelated function at some other time. In fact, attempting to insert the data on a database whose version was incremented since last page load will automatically trigger the upgradeneeded event for you.
While many of the online examples shove the database connection handle (your db var) into some global scope variable, this is also a bad practice that will lead to errors down the road. Only access the db var within your callbacks as a parameter. In other words, your openRequest.onsuccess function should pass the db variable to the query function. This also reduces the chances of any garbage collection issues later and leaving database connections open (which the designers of indexedDB allow for, but should generally be avoided).
If your movie ids are integers, it isn't clear to me why you are storing and retrieving them as strings. You can store integer values. You can pass an integer to store.get.
You are using for...in inappropriately. It will work, but for...in is intended for looping over the keys of object literals (like var x = {key:value}). Use a normal for loop or use array.foreach when iterating over your movies array.
As you found out in your fixed code, it is better to use request.onsuccess and request.onerror. There is also a transaction.oncomplete. But I am not sure there is a request.oncomplete. What happens is you are setting the oncomplete property of an IDBRequestObject but this does nothing since the code never triggers it.
DOM 11 usually signals you tried to access a table that does not exist or is in an incorrect state. Usually this happens due to mistakes elsewhere, like onupgradeneeded never getting called when connecting. It is confusing, but given the way your code is setup, basically the db gets created the first time your page loads, but then never gets created again, so while developing, if you made changes, but do not increment your db version, you will never see them.

Categories