Using localStorage and sessionStorage as tree of elements - javascript

During application refactoring I very lately found out about localStorage and sessionStorage are key-value storages, so question: is thee any JavaScript implementation for using localStorage, sessionStorage as JSON, and stay ability to easely edit them via browser debug tools?
Example:
We create some value for key application, it have sub-keys like settings, connection, they have subkeys for properties.
So, easy way to interact them like this:
if (localStorage.application.connection.URI.slice(0, 5) === "https") { ... }
And, if we need to destroy branch for properties and re-init them:
localStorage.application.connection = undefined;
Any way to do this? I know, I can use
if (localStorage.getItem("application.connection.URI").slice(0, 5) === "https") { ... }
And (thx to this answer How to remove localStorage data starting with a certain string?)
for (key in localStorage) {
if (key.substring(0,22) == 'application.connection') {
localStorage.removeItem(key);
}
}
But it is slightly hard to read and use.
Any suggestions? And sorry for my english.

A bit late but here you go: I implemented DotStorage as a hacky solution for this about a year ago.
It's neither maintained nor fully tested but it does the job.
...I just checked the repo: be aware that you need pako for this to work....
Don't use it for big things though as this is realized by automatically wrapping objects and their properties in proxies - implementing deep change detection by trapping everything.
Usage: like any other Javascript object, it's just persistent:
dotStorage.Hello = "World";
// reload page
console.assert(dotStorage.Hello == "World");
You can take a look at my JSFiddle based test file here
Example:
var myObj = {"New": "object"};
// save the object
dotStorage.refTest = myObj;
// get proxified instance
myObj = dotStorage.refTest;
// change nested properties
myObj.New = {"nested": {"otherObject": [0, 1]}};
console.log(JSON.stringify(dotStorage.refTest.New));
// reload the page ------------------------------------------
// change more nested properties
myObj.New.nested = 2;
// everything is still there
console.log(JSON.stringify(dotStorage.refTest.New));

Related

Im building a clicker game and localstorage is returning with nothing instead of saved data

So I'm building a clicker game on CodePen and I ran into a problem while coding my save/load functions. It seems as if my save function may not me working, but I'm not sure why.Here are the functions:
function savegame(){
localStorage.clicks = Number(document.getElementById('clicks').innerHTML);
localStorage.cps = document.getElementById('cps').innerHTML;
localStorage.clickPower =
Number(document.getElementById('clickpower').innerHTML);
localStorage.onecpow = Number(document.getElementById('1clickpowe').innerHTML);
localStorage.onecps = Number(document.getElementById('1cps').innerHTML);
localStorage.fivecps = Number(document.getElementById('5cps').innerHTML);
localStorage.fivecpow = Number(document.getElementById('5clickpower').innerHTML);
localStorage.tencps = document.getElementById('10cps').innerHTML;}
function loadgame(){document.getElementById('1clickpower').innerHTML = localStorage.getItem("onecpow");
document.getElementById('clicks').innerHTML = localStorage.getItem("clicks");
document.getElementById('cps').innerHTML = localStorage.getItem('cps');
document.getElementById('clickpower').innerHTML = localStorage.getItem('clickpower');
document.getElementById('5clickpower').innerHTML = localStorage.getItem('fivecpow');
document.getElementById('5cps').innerHTML = localStorage.getItem('fivecps');
document.getElementById('1cps').innerHTML = localStorage.getItem('onecps');
document.getElementById('10cps').innerHTML = localStorage.getItem('tencps');}
I forgot to clarify what I'm expecting the code to do. What this should do is save the players progress and upgrades, instead upon loading the saved data the players data is not loaded and returns many blank spaces where saved data should have been loaded.
Instead of doing localStorage.onecpow = "someValue" try
storage.setItem(onecpow, "someValue");
You can check the Storage docs here for more info
You are using 'localStorage' wrong. Have a look at this:
// writing to localStorage
localStorage.setItem('myId', value);
// reading from localStorage
var whatever = localStorage.getItem('myId');
// remove an item from localStorage
localStorage.removeItem('myId');
EDIT:
The problem is that you're mixing up 2 different approaches. You have to decide which one to use. Either the object-driven or the key-driven.
My example above is the key-driven approach. You should also be able to use the object-driven approach like this:
// writing to localStorage
localStorage.myObject = value;
// reading from localStorage
var whatever = localStorage.myObject;
Choose your style, but don't mix them. ;o)
I found a solution, though it's kind of strange how it works.
I added capital letters to the localStorage attributes and used localStorage.setItem and localStorage.getItem. Don't know why it works but it does.

Is localStorage thread safe?

I'm curious about the possibility of damaging localStorage entry by overwriting it in two browser tabs simultaneously. Should I create a mutex for local storage?
I was already thinking of such pseudo-class:
LocalStorageMan.prototype.v = LocalStorageMan.prototype.value = function(name, val) {
//Set inner value
this.data[name] = val;
//Delay any changes if the local storage is being changed
if(localStorage[this.name+"__mutex"]==1) {
setTimeout(function() {this.v(name, val);}, 1);
return null; //Very good point #Lightness Races in Orbit
}
//Lock the mutext to prevent overwriting
localStorage[this.name+"__mutex"] = 1;
//Save serialized data
localStorage[this.name] = this.serializeData;
//Allow usage from another tabs
localStorage[this.name+"__mutex"] = 0;
}
The function above implies local storage manager that is managing one specific key of the local storage - localStorage["test"] for example. I want to use this for greasomonkey userscripts where avoiding conlicts is a priority.
Yes, it is thread safe. However, your code isn't atomic and that's your problem there. I'll get to thread safety of localStorage but first, how to fix your problem.
Both tabs can pass the if check together and write to the item overwriting each other. The correct way to handle this problem is using StorageEvents.
These let you notify other windows when a key has changed in localStorage, effectively solving the problem for you in a built in message passing safe way. Here is a nice read about them. Let's give an example:
// tab 1
localStorage.setItem("Foo","Bar");
// tab 2
window.addEventListener("storage",function(e){
alert("StorageChanged!"); // this will run when the localStorage is changed
});
Now, what I promised about thread safety :)
As I like - let's observe this from two angles - from the specification and using the implementation.
The specification
Let's show it's thread safe by specification.
If we check the specification of Web Storage we can see that it specifically notes:
Because of the use of the storage mutex, multiple browsing contexts will be able to access the local storage areas simultaneously in such a manner that scripts cannot detect any concurrent script execution.
Thus, the length attribute of a Storage object, and the value of the various properties of that object, cannot change while a script is executing, other than in a way that is predictable by the script itself.
It even elaborates further:
Whenever the properties of a localStorage attribute's Storage object are to be examined, returned, set, or deleted, whether as part of a direct property access, when checking for the presence of a property, during property enumeration, when determining the number of properties present, or as part of the execution of any of the methods or attributes defined on the Storage interface, the user agent must first obtain the storage mutex.
Emphasis mine. It also notes that some implementors don't like this as a note.
In practice
Let's show it's thread safe in implementation.
Choosing a random browser, I chose WebKit (because I didn't know where that code is located there before). If we check at WebKit's implementation of Storage we can see that it has its fare share of mutexes.
Let's take it from the start. When you call setItem or assign, this happens:
void Storage::setItem(const String& key, const String& value, ExceptionCode& ec)
{
if (!m_storageArea->canAccessStorage(m_frame)) {
ec = SECURITY_ERR;
return;
}
if (isDisabledByPrivateBrowsing()) {
ec = QUOTA_EXCEEDED_ERR;
return;
}
bool quotaException = false;
m_storageArea->setItem(m_frame, key, value, quotaException);
if (quotaException)
ec = QUOTA_EXCEEDED_ERR;
}
Next, this happens in StorageArea:
void StorageAreaImpl::setItem(Frame* sourceFrame, const String& key, const String& value, bool& quotaException)
{
ASSERT(!m_isShutdown);
ASSERT(!value.isNull());
blockUntilImportComplete();
String oldValue;
RefPtr<StorageMap> newMap = m_storageMap->setItem(key, value, oldValue, quotaException);
if (newMap)
m_storageMap = newMap.release();
if (quotaException)
return;
if (oldValue == value)
return;
if (m_storageAreaSync)
m_storageAreaSync->scheduleItemForSync(key, value);
dispatchStorageEvent(key, oldValue, value, sourceFrame);
}
Note that blockUntilImportComplete here. Let's look at that:
void StorageAreaSync::blockUntilImportComplete()
{
ASSERT(isMainThread());
// Fast path. We set m_storageArea to 0 only after m_importComplete being true.
if (!m_storageArea)
return;
MutexLocker locker(m_importLock);
while (!m_importComplete)
m_importCondition.wait(m_importLock);
m_storageArea = 0;
}
They also went as far as add a nice note:
// FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so).
// Blocking everything until the import is complete is by far the simplest and safest thing to do, but
// there is certainly room for safe optimization: Key/length will never be able to make use of such an
// optimization (since the order of iteration can change as items are being added). Get can return any
// item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list
// of items the import should not overwrite. Clear can also work, but it'll need to kill the import
// job first.
Explaining this works, but it can be more efficient.
No, it's not. Mutex was removed from the spec, and this warning was added instead:
The localStorage getter provides access to shared state. This
specification does not define the interaction with other browsing
contexts in a multiprocess user agent, and authors are encouraged to
assume that there is no locking mechanism. A site could, for instance,
try to read the value of a key, increment its value, then write it
back out, using the new value as a unique identifier for the session;
if the site does this twice in two different browser windows at the
same time, it might end up using the same "unique" identifier for both
sessions, with potentially disastrous effects.
See HTML Spec: 12 Web storage

Java-style Set collection for Javascript

I need a Set that has the API similar to the Set in Java.
This implementation:
http://jsclass.jcoglan.com/set.html
Requires the use of RequireJS, which requires my Java brain to twist too much at the moment.
Posting a function that is the functionality for Set would be a great answer.
Or a link to a Google Set or some other tech giant who has created this code already.
What about Google's Closure? The name confused me but it has a set.
In my opinion whatever java.util.Set can achieve can be done using simple javascript object. I don't see why you need additional library:
// empty set
var basket = {};
// adding object to set
basket['apple'] = true;
basket['banana'] = true;
basket['orange'] = true;
basket['apple'] = true;
// iterating through set contents, should print:
// apple
// banana
// orange
for(var fruit in basket)
console.log(fruit);
// check if an element exist
if(basket['pineapple']) {
console.log('has pineapple');
} else {
console.log('no pineapple');
}
// remove element from set
delete basket['apple'];

Mainting the state of array across the entire app using singleton pattern

function MySingletonClass(arg) {
this.arr = [];
if ( arguments.callee._singletonInstance )
return arguments.callee._singletonInstance;
arguments.callee._singletonInstance = this;
this.Foo = function() {
this.arr.push(arg);
// ...
}
}
var a = new MySingletonClass()
var b = MySingletonClass()
Print( a === b ); // prints: true
My requirement is i am pushing objects to an array on each load of window, but when i open the next window the state of the array is not visible.
var arr = [];
arr.push("something");
// It gets pushed.
When i open the new window, the array's length becomes zero again.
There is no way to do this with JavaScript alone. JavaScript is just the language. It doesn't have any direct link to the app, the page or even the browser. JavaScript can be used (and is used) in many other situations, such as in server-side applications and as a plugin language for desktop apps.
Of course, when JavaScript is used in the browser, you do need a way to "communicate", as it were, with the content on page. For this you can use the Document Object Model (DOM) API, which is implemented by every browser that supports JavaScript. To communicate with the browser itself you can use window and other global object. These are sometimes referred to as the Browser Object Model (although it's not an official API).
Now that we know that; is there an API that allows us to maintain state between pages? Yes, there is. In fact, there are several:
HTML5's localStorage
Cookies
Take this example, using localStorage:
// On page 1:
localStorage.setItem("message", "Hello World!");
// On page 2:
var message = localStorage.getItem("message");
if (message !== null) {
alert(message);
}
Easy, right? Unfortunately, localStorage only accepts key/value pairs. To save an array, you'll need to convert it into a string first. You could do this, for example, using JSON:
// On both pages:
var arr = localStorage.getItem("arr");
if (arr === null) {
arr = [];
} else {
arr = JSON.parse(arr);
}
function saveArr() {
localStorage.setItem("arr", JSON.stringify(arr));
}
// On page 1:
console.log(arr); // []
arr.push("Hello");
arr.push("world!");
saveArr();
// On page 2:
console.log(arr); // ["Hello", "world!"]
Keep in mind, though, that localStorage and JSON are both fairly new, so only modern browsers support them. Have a look at emulating localStorage using cookies and at JSON2.js.
For data to persist across an application, there must be a database. Javascript cannot accomplish this because it is client side only and mostly intended as a way to render user interfaces.

New Element in MooTools by UID

Is it possible to instantiate an element on Mootools based on the automatic UID that mootools create?
EDIT: To give more info on what is going. I'm using https://github.com/browserstate/history.js to make a history within an ajax page. When I add a DOM element to it (which does not have an id), at some point it passes through a JSON.toString methods and what I have of the element now is just the uid.
I need to recreate the element based on this UID, how could I go about doing that? Do I need to first add it to the global storage to retrieve later? If so, how?
in view of edited question:
sorry, I fail to understand what you are doing.
you have an element. at some point the element is turned into an object that gets serialised (all of it? prototypes etc?). you then take that data and convert to an object again but want to preserve the uid? why?
I don't understand how the uid matters much here...
Using global browser storage also serialises to string so that won't help much. Are we talking survival of page loads here or just attach/detach/overwrite elements? If the latter, this can work with some tweaking.
(function() {
var Storage = {};
Element.implement({
saveElement: function() {
var uid = document.id(this).uid;
Storage[uid] = this;
return this;
}
});
this.restoreElement = function(uid) {
return Storage[uid] || null;
}
})();
var foo = document.id("foo"), uid = foo.uid;
console.log(uid);
foo.saveElement().addEvent("mouseenter", function() { alert("hi"); } );
document.id("container").set("html", "");
setTimeout(function() {
var newElement = restoreElement(uid);
if (newElement)
newElement.inject(document.body);
console.log(newElement.uid);
}, 2000);
http://jsfiddle.net/dimitar/7mwmu/1/
this will allow you to remove an element and restore it later.
keep in mind that i do container.set("html", ""); which is not a great practice.
if you do .empty(), it will GC the foo and it will wipe it's storage so the event won't survive. same for foo.destroy() - you can 'visually' restore the element but nothing linked to it will work (events or fx).
you can get around that by using event delegation, however.
also, you may want to store parent node etc so you can put it back to its previous place.

Categories