MongoDB-like JS library to store objects in client side indexedDB? - javascript

Given the following localStorage code (jsfiddle) :
// my new information
var data = {character: "中", totalMistakes: 0};
var han = data.character;
// create localStorage; Unpack, update, repackage knol
/* **** THIS IS THE SECTION TO CONVERT **** */
localStorage.knol = '{}'; // create pseudo object, as string
var knol = JSON.parse(localStorage.knol)
knol[han] = data;
localStorage.knol = JSON.stringify(knol);
// Print to check if all went well.
console.log('data: ',data)
console.log('han: ',han)
console.log('knol: ',knol)
console.log('localStorage.knol: ',localStorage.knol)
console.log('localStorage.knol: ',JSON.parse(localStorage.knol))
console.log('localStorage.knol[han]: ',JSON.parse(localStorage.knol)[han])
At the end, localStorage.knol is :
{
"中": {character: "中", totalMistakes: 0}
}
I'am looking for a Mongo-like js library to store data on client side indexedDB, with a syntax similar to MongoDB with which I'am already familiar.
How to convert localStorage code above into Mongo-like IndexedDB library syntax so to store an object ?
EDIT: I suggest minimongo, but any MongoDB-like library storing in indexedDB will do.

There is a variety of librairies available to do that.
Dexie.js
Using Dexie.js and its API (jsfiddle) :
<!-- Include dexie.js -->
<script src="https://unpkg.com/dexie#latest/dist/dexie.js"></script>
<script>
var db = new Dexie('MyDatabase');
// Define a schema
db.version(1).stores({ knol: 'character, totalMistakes' });
// Open the database
db.open().catch(function(error) { alert('Uh oh : ' + error); });
// or make a new one
db.knol.put({ character: '中', totalMistakes: 8 });
// Find some old friends
var mistakes = db.knol.where('totalMistakes');
//mistakes.above(6).each (function (item) { console.log (item); });
mistakes.aboveOrEqual(0).each (function (item) { console.log (item)});
</script>
Minimongo
I don't recommend it, but there is how to use it in web browsers
ZangoDB
(Exploration ongoing https://jsfiddle.net/vb92pecv/3/ )

Related

Using idb instead of localStorage

I'm following the Progressive Web App lab from Google and it says that it's using localStorage for simplicity but that we should change it to idb.
Basically, we want to store a list of cities to display their weather information.
I tried using plain idb following the info here but I think I'm too new to this and I couldn't get any of this. Am I supposed to do:
const dbPromise = idb.open('keyval-store', 1, upgradeDB => {
upgradeDB.createObjectStore('keyval');
});
and would keyval be the name of my variable where I would use keyval.get() or keyval.set() to get and store values?
I decided to move on to the simpler idbKeyval, I'm doing:
app.saveSelectedCities = function() {
var selectedCities = JSON.stringify(app.selectedCities);
idbKeyval.set(selectedCities);
};
instead of the localStorage example:
app.saveSelectedCities = function() {
var selectedCities = JSON.stringify(app.selectedCities);
localStorage.selectedCities = selectedCities;
};
and
app.selectedCities = idbKeyval.keys().then(keys => console.log(keys)).catch(err => console.log('It failed!', err));
instead of the localStorage example:
app.selectedCities = localStorage.selectedCities;
But my app is not loading any data, and in the developer tools console, I get:
app.js:314 Uncaught ReferenceError: idbKeyval is not defined(…)
I'm sure I'm missing something trivial but these are my first steps with javascript and the likes, so please, any help with any of the points touched here would be greatly appreciated!
Given the error you're seeing, it looks like you've forgotten to include the idb-keyval library.
I too was going through this, and wanted it to work with localForage. Took a bit, because I'm new to it too, but here is what I used for the save and load functions which made it all work.
// TODO add saveSelectedCities function here
// Save list of cities to localStorage
app.saveSelectedCities = function() {
var selectedCities = JSON.stringify(app.selectedCities);
localforage.setItem('selectedCities', selectedCities);
//localStorage.selectedCities = selectedCities;
}
localforage.getItem('selectedCities').then(function(cityList) {
app.selectedCities = cityList;
app.selectedCities.forEach(function(city) {
app.getForecast(city.key, city.label);
});
}).catch(function(err) {
app.updateForecastCard(initialWeatherForecast);
app.selectedCities = [
{key: initialWeatherForecast.key, label: initialWeatherForecast.label}
];
app.saveSelectedCities();
});

Assemble paginated ajax data in a Bacon FRP stream

I'm learning FRP using Bacon.js, and would like to assemble data from a paginated API in a stream.
The module that uses the data has a consumption API like this:
// UI module, displays unicorns as they arrive
beautifulUnicorns.property.onValue(function(allUnicorns){
console.log("Got "+ allUnicorns.length +" Unicorns");
// ... some real display work
});
The module that assembles the data requests sequential pages from an API and pushes onto the stream every time it gets a new data set:
// beautifulUnicorns module
var curPage = 1
var stream = new Bacon.Bus()
var property = stream.toProperty()
var property.onValue(function(){}) # You have to add an empty subscriber, otherwise future onValues will not receive the initial value. https://github.com/baconjs/bacon.js/wiki/FAQ#why-isnt-my-property-updated
var allUnicorns = [] // !!! stateful list of all unicorns ever received. Is this idiomatic for FRP?
var getNextPage = function(){
/* get data for subsequent pages.
Skipping for clarity */
}
var gotNextPage = function (resp) {
Array.prototype.push.apply(allUnicorns, resp) // just adds the responses to the existing array reference
stream.push(allUnicorns)
curPage++
if (curPage <= pageLimit) { getNextPage() }
}
How do I subscribe to the stream in a way that provides me a full list of all unicorns ever received? Is this flatMap or similar? I don't think I need a new stream out of it, but I don't know. I'm sorry, I'm new to the FRP way of thinking. To be clear, assembling the array works, it just feels like I'm not doing the idiomatic thing.
I'm not using jQuery or another ajax library for this, so that's why I'm not using Bacon.fromPromise
You also may wonder why my consuming module wants the whole set instead of just the incremental update. If it were just appending rows that could be ok, but in my case it's an infinite scroll and it should draw data if both: 1. data is available and 2. area is on screen.
This can be done with the .scan() method. And also you will need a stream that emits items of one page, you can create it with .repeat().
Here is a draft code (sorry not tested):
var itemsPerPage = Bacon.repeat(function(index) {
var pageNumber = index + 1;
if (pageNumber < PAGE_LIMIT) {
return Bacon.fromCallback(function(callback) {
// your method that talks to the server
getDataForAPage(pageNumber, callback);
});
} else {
return false;
}
});
var allItems = itemsPerPage.scan([], function(allItems, itemsFromAPage) {
return allItems.concat(itemsFromAPage);
});
// Here you go
allItems.onValue(function(allUnicorns){
console.log("Got "+ allUnicorns.length +" Unicorns");
// ... some real display work
});
As you noticed, you also won't need .onValue(function(){}) hack, and curPage external state.
Here is a solution using flatMap and fold. When dealing with network you have to remember that the data can come back in a different order than you sent the requests - that's why the combination of fold and map.
var pages = Bacon.fromArray([1,2,3,4,5])
var requests = pages.flatMap(function(page) {
return doAjax(page)
.map(function(value) {
return {
page: page,
value: value
}
})
}).log("Data received")
var allData = requests.fold([], function(arr, data) {
return arr.concat([data])
}).map(function(arr) {
// I would normally write this as a oneliner
var sorted = _.sortBy(arr, "page")
var onlyValues = _.pluck(sorted, "value")
var inOneArray = _.flatten(onlyValues)
return inOneArray
})
allData.log("All data")
function doAjax(page) {
// This would actually be Bacon.fromPromise($.ajax...)
// Math random to simulate the fact that requests can return out
// of order
return Bacon.later(Math.random() * 3000, [
"Page"+page+"Item1",
"Page"+page+"Item2"])
}
http://jsbin.com/damevu/4/edit

AngularJS and Restangular, trying to convert update method to API

I'm trying to convert my basic crud operations into an API that multiple components of my application can use.
I have successfully converted all methods, except the update one because it calls for each property on the object to be declared before the put request can be executed.
controller
$scope.update = function(testimonial, id) {
var data = {
name: testimonial.name,
message: testimonial.message
};
dataService.update(uri, data, $scope.id).then(function(response) {
console.log('Successfully updated!');
},
function(error) {
console.log('Error updating.');
});
}
dataService
dataService.update = function(uri, data, id) {
var rest = Restangular.one(uri, id);
angular.forEach(data, function(value, key) {
// needs to be in the format below
// rest.key = data.key
});
// needs to output something like this, depending on what the data is passed
// rest.name = data.name;
// rest.message = data.message;
return rest.put();
}
I tried to describe the problem in the codes comments, but to reiterate I cannot figure out how to generate something like rest.name = data.name; without specifying the name property because the update function shouldn't need to know the object properties.
Here is what the update method looked like before I started trying to make it usable by any of my components (this works)
Testimonial.update = function(testimonial, id) {
var rest = Restangular.one('testimonials', id);
rest.name = testimonial.name;
rest.message = testimonial.message;
return rest.put();
}
How can I recreate this without any specific properties parameters hard-coded in?
Also, my project has included lo-dash, if that helps, I don't know where to start with this problem. Thanks a ton for any advice!
Try like
angular.extend(rest,testimonial)
https://docs.angularjs.org/api/ng/function/angular.extend

How do I update data in indexedDB?

I have tried to get some information from W3C regarding the update of an objectStore item in a indexedDB database, but with not so much susccess.
I found here a way to do it, but it doesn't really work for me.
My implementation is something like this
DBM.activitati.edit = function(id, obj, callback){
var transaction = DBM.db.transaction(["activitati"], IDBTransaction.READ_WRITE);
var objectStore = transaction.objectStore("activitati");
var keyRange = IDBKeyRange.only(id);
objCursor = objectStore.openCursor(keyRange);
objCursor.onsuccess = function(e){
var cursor = e.target.result;
console.log(obj);
var request = cursor.update(obj);
request.onsuccess = function(){
callback();
}
request.onerror = function(e){
conosole.log("DBM.activitati.edit -> error " + e);
}
}
objCursor.onerror = function(e){
conosole.log("DBM.activitati.edit -> error " + e);
}
}
I have all DBM.activitati.(add | remove | getAll | getById | getByIndex) methods working, but I can not resolve this.
If you know how I can manage it, please, do tell!
Thank you!
Check out this jsfiddle for some examples on how to update IDB records. I worked on that with another StackOverflower -- it's a pretty decent standalone example of IndexedDB that uses indexes and does updates.
The method you seem to be looking for is put, which will either insert or update a record if there are unique indexes. In that example fiddle, it's used like this:
phodaDB.indexedDB.addUser = function(userObject){
//console.log('adding entry: '+entryTxt);
var db = phodaDB.indexedDB.db;
var trans = db.transaction(["userData"],IDBTransaction.READ_WRITE);
var store = trans.objectStore("userData");
var request = store.put(userObject);
request.onsuccess = function(e){
phodaDB.indexedDB.getAllEntries();
};
request.onerror = function(e){
console.log('Error adding: '+e);
};
};
For what it's worth, you've got some possible syntax errors, misspelling "console" in console.log as "conosole".
A bit late for an answer, but possible it helps others. I still stumbled -as i guess- over the same problem, but it's very simple:
If you want to INSERT or UPDATE records you use objectStore.put(object) (help)
If you only want to INSERT records you use objectStore.add(object) (help)
So if you use add(object), and a record key still exists in DB, it will not overwritten and fires error 0 "ConstraintError: Key already exists in the object store".
If you use put(object), it will be overwritten.
this is case of update infos of an user object
var transaction = db.transaction(["tab_user"], "readwrite");
var store = transaction.objectStore("tab_user");
var req = store.openCursor();
req.onerror = function(event) {
console.log("case if have an error");
};
req.onsuccess = function(event) {
var cursor = event.target.result;
if(cursor){
if(cursor.value.idUser == users.idUser){//we find by id an user we want to update
var user = {};
user.idUser = users.idUser ;
user.nom = users.nom ;
var res = cursor.update(user);
res.onsuccess = function(e){
console.log("update success!!");
}
res.onerror = function(e){
console.log("update failed!!");
}
}
cursor.continue();
}
else{
console.log("fin mise a jour");
}
}
I'm a couple of years late, but thought it'd be nice to add my two cents in.
First, check out BakedGoods if you don't want to deal with the complex IndexedDB API.
It's a library which establishes a uniform interface that can be used to conduct storage operations in all native, and some non-native client storage facilities. It also maintains the flexibility and options afforded to the user by each. Oh, and it's maintained by yours truly :) .
With it, placing one or more data items in an object store can be as simple as:
bakedGoods.set({
data: [{key: "key1", value: "value1"}, {key: "key2", value: "value2"}),
storageTypes: ["indexedDB"],
complete: function(byStorageTypeResultDataObj, byStorageTypeErrorObj){}
});
Now to answer the actual question...
Lets begin by aggregating the valuable information spread across the existing answers:
IDBObjectStore.put() adds a new record to the store, or updates an existing one
IDBObjectStore.add() adds a new record to the store
IDBCursor.update() updates the record at the current position of the cursor
As one can see, OP is using an appropriate method to update a record. There are, however, several things in his/her code, unrelated to the method, that are incorrect (with respect to the API today at least). I've identified and corrected them below:
var cursorRequest = objectStore.openCursor(keyRange); //Correctly define result as request
cursorRequest.onsuccess = function(e){ //Correctly set onsuccess for request
var objCursor = cursorRequest.result; //Get cursor from request
var obj = objCursor.value; //Get value from existing cursor ref
console.log(obj);
var request = objCursor.update(obj);
request.onsuccess = function(){
callback();
}
request.onerror = function(e){
console.log("DBM.activitati.edit -> error " + e); //Use "console" to log :)
}
}
cursorRequest.onerror = function(e){ //Correctly set onerror for request
console.log("DBM.activitati.edit -> error " + e); //Use "console" to log :)
}

JAWR i18n: unit testing javascript when using messages

Our application currently shares messages between the Java and Javascript side. They are stored as resource bundles in the class path, and we have a custom controller that returns all the messages as Json. The client side code look like this:
// This calls the controller to get all the messages
var messages = MessageBundle();
var text = messages.get('my.message', 1);
This is great because we can mock "messages" in our unit tests.
I want to start using JAWR for this, since we already use it for other things. The problem is JAWR generates the following Javascript object:
var text = messages.my.message(1);
This means the code cannot be unit tested anymore unless the unit tests also define a global "messages" variable with the right nested objects. Is there a way around this? Any idea how to extend JAWR to make this unit-testable?
Currently my work around is:
function messages() {
var args = Array.prototype.slice.call(arguments);
var messageId = args.shift();
var messageFunc = window.messages;
messageId.split('.').forEach(function(part) {
messageFunc = messageFunc[part];
});
return messageFunc(args);
}
// Same syntax as the old one, but uses the JAWR object behind the scenes
// This function is easy to mock for a unit test
var text = messages('my.message', 1);
Thanks for any ideas!
Maybe next samples can help you.
1)
function messagesTester(funcPath,id) {
var args=funcPath.split('.'),root=window.messages;
for(var i=0;i<args.length;i++)root=root[args[i]];
return root(id);
// or if more that one parameter for *func*, then, for example:
// return root.apply(null,Array.prototype.slice(arguments,1));
}
var text = messagesTester('my.message',1);
2)
function messagesTester(funcPath) {
var args=funcPath.split('.'),root=window.messages;
for(var i=0;i<args.length;i++)root=root[args[i]];
return root;
}
// var text = messagesTester('my.message')( /*arguments list*/ );
var text = messagesTester('my.message')(1);

Categories

Resources