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.
Related
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));
Im working on a heavily javascript based site with multiple language settings. The languages are stored in an external file, f e app.en.js and app.de.js. Both of these define the lang Object:
lang = {
introText0 : 'Lorem',
introText1 : 'Ipsum',
introText2 : 'Dolor'
}
Now i have a function responsible for changing the language:
function changeLanguage(language) {
delete lang;
if(language === 1) { $.getScript('app.en.js'); }
if(language === 2) { $.getScript('app.de.js'); }
}
So far so good. This works in theory. All over the app are references to the lang Object, replacing every text. Every text that gets referenced is being updated by this change, however, there are arrays that also refer to these, and they don't update properly.
var upgradeItems = new Array();
upgradeItems[0] = new Array(lang.upgradeItem0, 13, 5, true);
I take it that the garbage Collection of JavaScript only actually deletes unreferred properties of an Object, and since these got referred before the changeLanguage() happened, they stay in the app. How do i work around this and 'refresh' these Arrays?
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
I want to have singleton kind of object whose value gets changed during multiple events across multiple pages. This object is used to bind the ui in various pages.
I have a 'main.js' file :
var obj = { flag : false } ;
// call back method
function click() {
obj.flag = true;
}
and in my next.js file
// call back method
function click() {
alert(obj.flag); // **alerts false** . It should alert **true** instead.
}
Is there a way to persist the value other than using the window.name property ? or am I following the wrong approach
You can use HTML5 localStorage.
As described in the documentations (Safari, Mozilla etc.), localStorage supports string key/value pairs.
Therefore you need to use JSON.stringify to save your object in the storage.
var obj = { flag : false };
// Save the object in the storage
localStorage.setItem('obj', JSON.stringify(obj));
// Get the object from storage
var objectData = localStorage.getItem('obj');
var originalObject = JSON.parse(objectData );
alert(originalObject.flag);
See the following fiddle: http://jsfiddle.net/FdhzU/
I'm aware that there is a Cross site forgery attack that can be performed on a request that returns an array by overloading the Array constructor. For example, suppose I have a site with a URL:
foo.com/getJson
that returns:
['Puff the Dragon', 'Credit Card #']
This would normally be Javascript eval'd by my own site after an XHR request, but another site can sniff this data by including something like:
<script>
function Array() {
var arr = this;
var i = 0;
var next = function(val) {
arr[i++] setter = next;
document.write(val);
};
this[i++] setter = next;
}
</script>
<script src="http://foo.com/getJson"></script>
My question is, can the same thing be done when the request returns a Javascript object? i.e.
{ name: 'Puff the Dragon', cc: 'Credit Card #' }
I couldn't figure out a way to do this, but maybe I'm missing something. I know there are better solutions to protect my site, like using the while(1) hack or requiring an auth token in the URL, but I'm trying to figure out if this sort of security hole exists.
The sources I've seen, such as Haacked and Hackademix, specifically indicate that root objects are safe (presumably in all major browsers). This is because a script can not start with an object literal. By default, ASP.NET wraps both objects and arrays with a d prefix, but I think this is just to simplify the client library.
It looks like from the Ecmascript spec, the JSON object shouldn't be treated as a valid Javascript program:
"Note that an ExpressionStatement
cannot start with an opening curly
brace because that might make it
ambiguous with a Block.
So assuming that all browser implement this correctly, a response like { name: 'Puff the Dragon', cc: 'Credit Card #' } won't be executed as valid Javascript. However expressions like ({name: 'Puff the Dragon', cc: 'Credit Card #' }) and {['Puff the Dragon', 'Credit Card #']} will.
You could use the same technique for Object. It wouldn't affect the prototype chain, so it wouldn't be inherited by all objects. But you could, for example, log all new objects getting created with this:
function Object() {
var obj = this;
if (window.objectarray === undefined) {
window.objectarray = [];
}
window.objectarray.push(this);
return this;
}
Any time code on your page uses new Object(), it would get written to window.objectarray -- even if it were created in a private scope. So, for example, look at this code:
var Account = function() {
var createToken = function() {
var objToken = new Object();
objToken.timestamp = new Date().getTime();
objToken.securestring = "abc123";
return objToken.timestamp + objToken.securestring;
}
var objPrivate = new Object();
objPrivate.bankaccount="123-456789";
objPrivate.token = createToken();
};
var myAccount = new Account();
In this case, if you create a new account with new Account(), a token will be created using private properties (and maybe methods) and nothing about myAccount is left hanging outside in public. But both 'objectToken' and objPrivate will be logged to window.objectarray.